Repository: datreeio/datree
Pull request #43 (2021-05-23 10:55:13)

feat: Supporting multiple steps validation
* Integrate kubeconform into the project
* Seperate evaluation into several steps. Every step will filter out files that don't qualify for the next one
* Refactor project


Patch:
diff --git a/.gitignore b/.gitignore
index f68e458..cac91df 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@ vendor
 datree
 .DS_STORE
 .vscode
+.idea
diff --git a/Makefile b/Makefile
index 14533e3..8aaaec5 100644
--- a/Makefile
+++ b/Makefile
@@ -4,6 +4,9 @@ run:
 test:
 	go test ./...
 
+build:
+	go build -tags staging -ldflags="-X github.com/datreeio/datree/cmd.CliVersion=0.0.1"
+
 create-bin:
 	goreleaser --snapshot --skip-publish --rm-dist
 
diff --git a/bl/evaluation/evaluator.go b/bl/evaluation/evaluator.go
new file mode 100644
index 0000000..65d7e1d
--- /dev/null
+++ b/bl/evaluation/evaluator.go
@@ -0,0 +1,178 @@
+package evaluation
+
+import (
+	"github.com/datreeio/datree/bl/validation"
+	"github.com/datreeio/datree/pkg/cliClient"
+	"github.com/datreeio/datree/pkg/extractor"
+)
+
+type CLIClient interface {
+	RequestEvaluation(request *cliClient.EvaluationRequest) (*cliClient.EvaluationResponse, error)
+	CreateEvaluation(request *cliClient.CreateEvaluationRequest) (int, error)
+	UpdateEvaluationValidation(request *cliClient.UpdateEvaluationValidationRequest) error
+}
+
+type Evaluator struct {
+	cliClient             CLIClient
+	osInfo                *OSInfo
+	extractConfigurations func(path string) (*extractor.FileConfiguration, *extractor.Error)
+}
+
+func New(c CLIClient) *Evaluator {
+	return &Evaluator{
+		cliClient: c,
+		osInfo:    NewOSInfo(),
+	}
+}
+
+type EvaluationResults struct {
+	FileNameRuleMapper map[string]map[int]*Rule
+	Summary            struct {
+		RulesCount       int
+		TotalFailedRules int
+		FilesCount       int
+		TotalPassedCount int
+	}
+}
+
+type Error struct {
+	Message  string
+	Filename string
+}
+
+func (e *Evaluator) CreateEvaluation(cliId string, cliVersion string, k8sVersion string) (int, error) {
+	evaluationId, err := e.cliClient.CreateEvaluation(&cliClient.CreateEvaluationRequest{
+		K8sVersion: k8sVersion,
+		CliId:      cliId,
+		Metadata: &cliClient.Metadata{
+			CliVersion:      cliVersion,
+			Os:              e.osInfo.OS,
+			PlatformVersion: e.osInfo.PlatformVersion,
+			KernelVersion:   e.osInfo.KernelVersion,
+		},
+	})
+
+	return evaluationId, err
+}
+
+func (e *Evaluator) Evaluate(validFilesPathsChan chan string, invalidFilesPathsChan chan *validation.InvalidFile, evaluationId int) (*EvaluationResults, []*validation.InvalidFile, []*extractor.FileConfiguration, []*Error, error) {
+	filesConfigurations, invalidFiles, extractionErrors := e.extractFilesConfigurations(validFilesPathsChan, invalidFilesPathsChan)
+
+	invalidFilesPaths := []*string{}
+	for _, file := range invalidFiles {
+		invalidFilesPaths = append(invalidFilesPaths, &file.Path)
+	}
+
+	if len(invalidFiles) > 0 {
+		stopEvaluation := len(filesConfigurations) == 0 // NOTICE: validFilesPathsChan surely closed and empty
+		err := e.cliClient.UpdateEvaluationValidation(&cliClient.UpdateEvaluationValidationRequest{
+			EvaluationId:   evaluationId,
+			InvalidFiles:   invalidFilesPaths,
+			StopEvaluation: stopEvaluation,
+		})
+
+		if stopEvaluation {
+			return nil, invalidFiles, filesConfigurations, extractionErrors, err
+		}
+	}
+
+	if len(filesConfigurations) > 0 {
+		res, err := e.cliClient.RequestEvaluation(&cliClient.EvaluationRequest{
+			EvaluationId: evaluationId,
+			Files:        filesConfigurations,
+		})
+		if err != nil {
+			return nil, invalidFiles, filesConfigurations, extractionErrors, err
+		}
+
+		results := e.formatEvaluationResults(res.Results, len(filesConfigurations))
+		return results, invalidFiles, filesConfigurations, extractionErrors, nil
+	}
+
+	return nil, invalidFiles, filesConfigurations, extractionErrors, nil
+}
+
+func (e *Evaluator) extractFilesConfigurations(validFilesPathsChan chan string, invalidFilesPathsChan chan *validation.InvalidFile) ([]*extractor.FileConfiguration, []*validation.InvalidFile, []*Error) {
+	invalidFiles := []*validation.InvalidFile{}
+	var files []*extractor.FileConfiguration
+	var errors []*Error
+
+	readFromValidDone := false
+	readFromInvalidDone := false
+	for {
+		select {
+		case path, ok := <-validFilesPathsChan:
+			if !ok {
+				readFromValidDone = true
+			} else {
+				file, err := extractor.ExtractConfiguration(path)
+				if file != nil {
+					files = append(files, &extractor.FileConfiguration{
+						FileName:       file.FileName,
+						Configurations: file.Configurations,
+					})
+				}
+				if err != nil {
+					errors = append(errors, &Error{
+						Message:  err.Message,
+						Filename: err.Filename,
+					})
+				}
+			}
+		case file, ok := <-invalidFilesPathsChan:
+			if !ok {
+				readFromInvalidDone = true
+			} else {
+				invalidFiles = append(invalidFiles, file)
+			}
+		}
+		if readFromValidDone && readFromInvalidDone {
+			break
+		}
+	}
+
+	return files, invalidFiles, errors
+}
+
+func (e *Evaluator) formatEvaluationResults(evaluationResults []*cliClient.EvaluationResult, filesCount int) *EvaluationResults {
+	mapper := make(map[string]map[int]*Rule)
+
+	totalRulesCount := len(evaluationResults)
+	totalFailedCount := 0
+	totalPassedCount := filesCount
+
+	for _, result := range evaluationResults {
+		for _, match := range result.Results.Matches {
+			// file not already exists in mapper
+			if _, exists := mapper[match.FileName]; !exists {
+				mapper[match.FileName] = make(map[int]*Rule)
+				totalPassedCount = totalPassedCount - 1
+			}
+
+			// file and rule not already exists in mapper
+			if _, exists := mapper[match.FileName][result.Rule.ID]; !exists {
+				totalFailedCount++
+				mapper[match.FileName][result.Rule.ID] = &Rule{ID: result.Rule.ID, Name: result.Rule.Name, FailSuggestion: result.Rule.FailSuggestion, Count: 0}
+			}
+
+			mapper[match.FileName][result.Rule.ID].IncrementCount()
+		}
+	}
+
+	results := &EvaluationResults{
+		FileNameRuleMapper: mapper,
+		Summary: struct {
+			RulesCount       int
+			TotalFailedRules int
+			FilesCount       int
+			TotalPassedCount int
+		}{
+			RulesCount:       totalRulesCount,
+			TotalFailedRules: totalFailedCount,
+			FilesCount:       filesCount,
+			TotalPassedCount: totalPassedCount,
+		},
+	}
+
+	return results
+}
diff --git a/bl/osInfo.go b/bl/evaluation/osInfo.go
similarity index 87%
rename from bl/osInfo.go
rename to bl/evaluation/osInfo.go
index 4a5e39a..437b5b1 100644
--- a/bl/osInfo.go
+++ b/bl/evaluation/osInfo.go
@@ -1,4 +1,4 @@
-package bl
+package evaluation
 
 import "github.com/shirou/gopsutil/host"
 
@@ -8,7 +8,7 @@ type OSInfo struct {
 	KernelVersion   string
 }
 
-func NewOsInfo() *OSInfo {
+func NewOSInfo() *OSInfo {
 	infoStat, _ := host.Info()
 	return &OSInfo{
 		OS:              infoStat.OS,
diff --git a/bl/evaluation/printer.go b/bl/evaluation/printer.go
new file mode 100644
index 0000000..c66188c
--- /dev/null
+++ b/bl/evaluation/printer.go
@@ -0,0 +1,183 @@
+package evaluation
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/datreeio/datree/bl/validation"
+	"os"
+	"path/filepath"
+
+	"github.com/datreeio/datree/pkg/printer"
+	"gopkg.in/yaml.v2"
+)
+
+type Printer interface {
+	PrintWarnings(warnings []printer.Warning)
+	PrintSummaryTable(summary printer.Summary)
+	PrintEvaluationSummary(summary printer.EvaluationSummary)
+}
+
+// func PrintAllResults(results *EvaluationResults /*, invalidFiles*/) {
+// 	// TODO: foreach invalid file
+// }
+
+// url := "https://app.datree.io/login?cliId=" + cliId
+func PrintResults(results *EvaluationResults, invalidFiles []*validation.InvalidFile, evaluationSummary printer.EvaluationSummary, loginURL string, outputFormat string, printer Printer) error {
+	switch {
+	case outputFormat == "json":
+		return jsonOutput(results)
+	case outputFormat == "yaml":
+		return yamlOutput(results)
+	default:
+		return textOutput(results, invalidFiles, evaluationSummary, loginURL, printer)
+	}
+}
+
+func jsonOutput(results *EvaluationResults) error {
+	jsonOutput, err := json.Marshal(results)
+	if err != nil {
+		fmt.Println(err)
+		return err
+	}
+
+	fmt.Println(string(jsonOutput))
+	return nil
+}
+
+func yamlOutput(results *EvaluationResults) error {
+	yamlOutput, err := yaml.Marshal(results)
+	if err != nil {
+		fmt.Println(err)
+		return err
+	}
+
+	fmt.Println(string(yamlOutput))
+	return nil
+}
+
+func textOutput(results *EvaluationResults, invalidFiles []*validation.InvalidFile, evaluationSummary printer.EvaluationSummary, url string, printer Printer) error {
+	pwd, err := os.Getwd()
+	if err != nil {
+		return err
+	}
+
+	warnings, err := parseToPrinterWarnings(results, invalidFiles, pwd)
+	if err != nil {
+		fmt.Println(err)
+		return err
+	}
+
+	printer.PrintWarnings(warnings)
+
+	summary := parseEvaluationResultsToSummary(results, evaluationSummary, url)
+
+	printer.PrintEvaluationSummary(evaluationSummary)
+
+	printer.PrintSummaryTable(summary)
+
+	return nil
+}
+
+func parseToPrinterWarnings(results *EvaluationResults, invalidFiles []*validation.InvalidFile, pwd string) ([]printer.Warning, error) {
+	var warnings = []printer.Warning{}
+
+	for _, invalidFile := range invalidFiles {
+		warnings = append(warnings, printer.Warning{
+			Title:   fmt.Sprintf(">>  File: %s\n", invalidFile.Path),
+			Details: []printer.WarningInfo{},
+			ValidationInfo: printer.ValidationInfo{
+				IsValid:          false,
+				ValidationErrors: invalidFile.ValidationErrors,
+				K8sVersion:       "1.18.0",
+			},
+		})
+	}
+
+	if results != nil {
+		for fileName, rules := range results.FileNameRuleMapper {
+			var warningDetails = []printer.WarningInfo{}
+			for _, rule := range rules {
+				details := printer.WarningInfo{
+					Caption:     rule.Name,
+					Occurrences: rule.Count,
+					Suggestion:  rule.FailSuggestion,
+				}
+
+				warningDetails = append(warningDetails, details)
+			}
+
+			relativePath, _ := filepath.Rel(pwd, fileName)
+
+			warnings = append(warnings, printer.Warning{
+				Title:   fmt.Sprintf(">>  File: %s\n", relativePath),
+				Details: warningDetails,
+				ValidationInfo: printer.ValidationInfo{
+					IsValid:          true,
+					ValidationErrors: []error{},
+					K8sVersion:       "1.18.0",
+				},
+			})
+		}
+	}
+
+	return warnings, nil
+}
+
+type OutputTitle int
+
+const (
+	EnabledRules OutputTitle = iota
+	EvaluatedConfigurations
+	TotalRulesEvaluated
+	SeeAll
+	TotalRulesPassed
+	TotalRulesFailed
+)
+
+func (t OutputTitle) String() string {
+	return [...]string{
+		"Enabled rules in policy “default”",
+		"Configs tested against policy",
+		"Total rules evaluated",
+		"See all rules in policy",
+		"Total rules passed",
+		"Total rules failed"}[t]
+}
+func parseEvaluationResultsToSummary(results *EvaluationResults, evaluationSummary printer.EvaluationSummary, loginURL string) printer.Summary {
+	filesCount := evaluationSummary.FilesCount
+	rulesCount := 0
+	totalRulesEvaluated := 0
+	totalFailedRules := 0
+	totalPassedRules := 0
+
+	if results != nil {
+		rulesCount = results.Summary.RulesCount
+		totalRulesEvaluated = results.Summary.RulesCount * results.Summary.FilesCount
+		totalFailedRules = results.Summary.TotalFailedRules
+		totalPassedRules = totalRulesEvaluated - totalFailedRules
+	}
+
+	var rulesCountRightCol string
+	if rulesCount == 0 {
+		rulesCountRightCol = fmt.Sprint("N/A")
+	} else {
+		rulesCountRightCol = fmt.Sprint(rulesCount)
+	}
+
+	plainRows := []printer.SummaryItem{
+		{LeftCol: EnabledRules.String(), RightCol: rulesCountRightCol, RowIndex: 0},
+		{LeftCol: EvaluatedConfigurations.String(), RightCol: fmt.Sprint(filesCount), RowIndex: 1},
+		{LeftCol: TotalRulesEvaluated.String(), RightCol: fmt.Sprint(totalRulesEvaluated), RowIndex: 2},
+		{LeftCol: SeeAll.String(), RightCol: loginURL, RowIndex: 5},
+	}
+
+	successRow := printer.SummaryItem{LeftCol: TotalRulesPassed.String(), RightCol: fmt.Sprint(totalPassedRules), RowIndex: 4}
+	errorRow := printer.SummaryItem{LeftCol: TotalRulesFailed.String(), RightCol: fmt.Sprint(totalFailedRules), RowIndex: 3}
+
+	summary := &printer.Summary{
+		ErrorRow:   errorRow,
+		SuccessRow: successRow,
+		PlainRows:  plainRows,
+	}
+	return *summary
+}
diff --git a/bl/evaluation/rule.go b/bl/evaluation/rule.go
new file mode 100644
index 0000000..5948fad
--- /dev/null
+++ b/bl/evaluation/rule.go
@@ -0,0 +1,12 @@
+package evaluation
+
+type Rule struct {
+	ID             int
+	Name           string
+	FailSuggestion string
+	Count          int
+}
+
+func (rp *Rule) IncrementCount() {
+	rp.Count++
+}
diff --git a/bl/evaluator.go b/bl/evaluator.go
deleted file mode 100644
index caa8aa2..0000000
--- a/bl/evaluator.go
+++ /dev/null
@@ -1,251 +0,0 @@
-package bl
-
-import (
-	"encoding/json"
-	"fmt"
-	"os"
-	"path/filepath"
-
-	"github.com/datreeio/datree/pkg/cliClient"
-	"github.com/datreeio/datree/pkg/printer"
-	"github.com/datreeio/datree/pkg/propertiesExtractor"
-	"gopkg.in/yaml.v3"
-)
-
-type Printer interface {
-	PrintWarnings(warnings []printer.Warning)
-	PrintSummaryTable(summary printer.Summary)
-}
-type CLIClient interface {
-	RequestEvaluation(cliClient.EvaluationRequest) (cliClient.EvaluationResponse, error)
-}
-
-type PropertiesExtractor interface {
-	ReadFilesFromPaths(paths []string, conc int) ([]*propertiesExtractor.FileProperties, []propertiesExtractor.FileError, []error)
-}
-
-type Evaluator struct {
-	propertiesExtractor PropertiesExtractor
-	cliClient           CLIClient
-	printer             Printer
-	osInfo              *OSInfo
-}
-
-func CreateNewEvaluator(pe PropertiesExtractor, c CLIClient, p Printer) *Evaluator {
-	return &Evaluator{
-		propertiesExtractor: pe,
-		cliClient:           c,
-		printer:             p,
-		osInfo:              NewOsInfo(),
-	}
-}
-
-type EvaluationResults struct {
-	FileNameRuleMapper map[string]map[int]*Rule
-	Summary            struct {
-		RulesCount       int
-		TotalFailedRules int
-		FilesCount       int
-	}
-}
-
-type UserAgent struct {
-	OS              string
-	PlatformVersion string
-	KernelVersion   string
-}
-
-func (e *Evaluator) Evaluate(paths []string, cliId string, evaluationConc int, cliVersion string) (*EvaluationResults, []propertiesExtractor.FileError, error) {
-	files, fileErrors, errors := e.propertiesExtractor.ReadFilesFromPaths(paths, evaluationConc)
-	if len(errors) > 0 {
-		return nil, fileErrors, fmt.Errorf("failed evaluation with the following errors: %s", errors)
-	}
-
-	if len(files) == 0 {
-		return nil, fileErrors, fmt.Errorf("no files detected")
-	}
-
-	var filesProperties []propertiesExtractor.FileProperties
-
-	for _, file := range files {
-		filesProperties = append(filesProperties, *file)
-	}
-
-	evaluationRequest := cliClient.EvaluationRequest{
-		CliId: cliId,
-		Metadata: cliClient.Metadata{
-			CliVersion:      cliVersion,
-			Os:              e.osInfo.OS,
-			PlatformVersion: e.osInfo.PlatformVersion,
-			KernelVersion:   e.osInfo.KernelVersion,
-		},
-		Files: filesProperties,
-	}
-
-	res, err := e.cliClient.RequestEvaluation(evaluationRequest)
-	if err != nil {
-		return nil, fileErrors, err
-	}
-
-	results := e.aggregateEvaluationResults(res.Results, len(files))
-
-	return results, fileErrors, nil
-}
-
-func (e *Evaluator) PrintResults(results *EvaluationResults, cliId string, output string) error {
-	if output == "json" {
-		jsonOutput, err := json.Marshal(results)
-		if err != nil {
-			fmt.Println(err)
-			return err
-		}
-
-		fmt.Println(string(jsonOutput))
-		return nil
-	}
-
-	if output == "yaml" {
-		yamlOutput, err := yaml.Marshal(results)
-		if err != nil {
-			fmt.Println(err)
-			return err
-		}
-
-		fmt.Println(string(yamlOutput))
-		return nil
-	}
-
-	warnings, err := e.parseEvaluationResultsToWarnings(results)
-	if err != nil {
-		fmt.Println(err)
-		return err
-	}
-	e.printer.PrintWarnings(warnings)
-
-	configurePolicyLink := "https://app.datree.io/login?cliId=" + cliId
-	summary := e.parseEvaluationResultsToSummary(results, configurePolicyLink)
-
-	e.printer.PrintSummaryTable(summary)
-
-	if results.Summary.TotalFailedRules > 0 {
-		return fmt.Errorf("failed rules count is %d (>0)", results.Summary.TotalFailedRules)
-	}
-	return nil
-}
-
-func (e *Evaluator) PrintFileParsingErrors(errors []propertiesExtractor.FileError) {
-	if len(errors) > 0 {
-		fmt.Println("The following files failed:")
-
-		for _, fileError := range errors {
-			fmt.Printf("\n\tFilename: %s\n\tError: %s\n\t---------------------", fileError.Filename, fileError.Message)
-		}
-	}
-}
-
-func (e *Evaluator) aggregateEvaluationResults(evaluationResults []cliClient.EvaluationResult, filesCount int) *EvaluationResults {
-	mapper := make(map[string]map[int]*Rule)
-
-	totalRulesCount := len(evaluationResults)
-	totalFailedCount := 0
-
-	for _, result := range evaluationResults {
-		for _, match := range result.Results.Matches {
-			// file not already exists in mapper
-			if _, exists := mapper[match.FileName]; !exists {
-				mapper[match.FileName] = make(map[int]*Rule)
-			}
-
-			// file and rule not already exists in mapper
-			if _, exists := mapper[match.FileName][result.Rule.ID]; !exists {
-				totalFailedCount++
-				mapper[match.FileName][result.Rule.ID] = &Rule{ID: result.Rule.ID, Name: result.Rule.Name, FailSuggestion: result.Rule.FailSuggestion, Count: 0}
-			}
-
-			mapper[match.FileName][result.Rule.ID].IncrementCount()
-		}
-	}
-
-	results := &EvaluationResults{
-		FileNameRuleMapper: mapper,
-		Summary: struct {
-			RulesCount       int
-			TotalFailedRules int
-			FilesCount       int
-		}{
-			RulesCount:       totalRulesCount,
-			TotalFailedRules: totalFailedCount,
-			FilesCount:       filesCount,
-		},
-	}
-
-	return results
-}
-
-func (e *Evaluator) parseEvaluationResultsToWarnings(results *EvaluationResults) ([]printer.Warning, error) {
-	pwd, err := os.Getwd()
-	if err != nil {
-		return nil, err
-	}
-
-	warnings := []printer.Warning{}
-
-	for fileName, rules := range results.FileNameRuleMapper {
-		relativePath, _ := filepath.Rel(pwd, fileName)
-		title := fmt.Sprintf(">>  File: %s\n", relativePath)
-
-		warning := printer.Warning{
-			Title: title,
-		}
-
-		for _, rule := range rules {
-			details := struct {
-				Caption     string
-				Occurrences int
-				Suggestion  string
-			}{
-				Caption:     rule.Name,
-				Occurrences: rule.Count,
-				Suggestion:  rule.FailSuggestion,
-			}
-
-			warning.Details = append(warning.Details, details)
-		}
-
-		warnings = append(warnings, warning)
-	}
-
-	return warnings, nil
-}
-
-func (e *Evaluator) parseEvaluationResultsToSummary(results *EvaluationResults, loginURL string) printer.Summary {
-
-	totalRulesEvaluated := results.Summary.RulesCount * results.Summary.FilesCount
-	plainRows := []printer.SummaryItem{
-		{LeftCol: "Enabled rules in policy “default”", RightCol: fmt.Sprint(results.Summary.RulesCount), RowIndex: 0},
-		{LeftCol: "Configs tested against policy", RightCol: fmt.Sprint(results.Summary.FilesCount), RowIndex: 1},
-		{LeftCol: "Total rules evaluated", RightCol: fmt.Sprint(totalRulesEvaluated), RowIndex: 2},
-		{LeftCol: "See all rules in policy", RightCol: loginURL, RowIndex: 5},
-	}
-
-	successRow := printer.SummaryItem{LeftCol: "Total rules passed", RightCol: fmt.Sprint(totalRulesEvaluated - results.Summary.TotalFailedRules), RowIndex: 4}
-	errorRow := printer.SummaryItem{LeftCol: "Total rules failed", RightCol: fmt.Sprint(results.Summary.TotalFailedRules), RowIndex: 3}
-
-	summary := &printer.Summary{
-		ErrorRow:   errorRow,
-		SuccessRow: successRow,
-		PlainRows:  plainRows,
-	}
-	return *summary
-}
-
-type Rule struct {
-	ID             int
-	Name           string
-	FailSuggestion string
-	Count          int
-}
-
-func (rp *Rule) IncrementCount() {
-	rp.Count++
-}
diff --git a/bl/files/files.go b/bl/files/files.go
new file mode 100644
index 0000000..056c86f
--- /dev/null
+++ b/bl/files/files.go
@@ -0,0 +1,37 @@
+package files
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+)
+
+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 ToAbsolutePaths(paths []string) chan string {
+	pathsChan := make(chan string)
+
+	go func() {
+		for _, p := range paths {
+			absolutePath, err := toAbsolutePath(p)
+			if err == nil {
+				pathsChan <- absolutePath
+			}
+		}
+		close(pathsChan)
+	}()
+
+	return pathsChan
+}
diff --git a/bl/messager/messager.go b/bl/messager/messager.go
new file mode 100644
index 0000000..4dfcfcb
--- /dev/null
+++ b/bl/messager/messager.go
@@ -0,0 +1,49 @@
+package messager
+
+import (
+	"github.com/datreeio/datree/pkg/cliClient"
+)
+
+type MessagesClient interface {
+	GetVersionMessage(cliVersion string, timeout int) (*cliClient.VersionMessage, error)
+}
+
+type Messager struct {
+	defaultTimeout int
+	messagesClient MessagesClient
+}
+
+func New(c MessagesClient) *Messager {
+	return &Messager{
+		defaultTimeout: 900,
+		messagesClient: c,
+	}
+}
+
+type VersionMessage struct {
+	CliVersion   string
+	MessageText  string
+	MessageColor string
+}
+
+func (m *Messager) LoadVersionMessages(messages chan *VersionMessage, cliVersion string) {
+	go func() {
+		msg, _ := m.messagesClient.GetVersionMessage(cliVersion, 900)
+		if msg != nil {
+			messages <- m.toVersionMessage(msg)
+		}
+		close(messages)
+	}()
+}
+
+func (m *Messager) toVersionMessage(msg *cliClient.VersionMessage) *VersionMessage {
+	if msg != nil {
+		return &VersionMessage{
+			CliVersion:   msg.CliVersion,
+			MessageText:  msg.MessageText,
+			MessageColor: msg.MessageColor,
+		}
+	}
+
+	return nil
+}
diff --git a/bl/validation/k8sValidator.go b/bl/validation/k8sValidator.go
new file mode 100644
index 0000000..b9b8e9a
--- /dev/null
+++ b/bl/validation/k8sValidator.go
@@ -0,0 +1,88 @@
+package validation
+
+import (
+	"fmt"
+	"io"
+	"os"
+
+	"github.com/datreeio/datree/bl/files"
+	kubeconformValidator "github.com/yannh/kubeconform/pkg/validator"
+)
+
+type ValidationClient interface {
+	Validate(filename string, r io.ReadCloser) []kubeconformValidator.Result
+}
+
+type K8sValidator struct {
+	validationClient ValidationClient
+}
+
+func New(k8sVersion string) *K8sValidator {
+	kubconformClient := newKubconformValidator(k8sVersion)
+	return &K8sValidator{
+		validationClient: kubconformClient,
+	}
+}
+
+type InvalidFile struct {
+	Path             string
+	ValidationErrors []error
+}
+
+func (val *K8sValidator) ValidateResources(paths []string) (chan string, chan *InvalidFile, chan error) {
+	pathsChan := files.ToAbsolutePaths(paths)
+
+	errorChan := make(chan error)
+	validFilesPathChan := make(chan string)
+	invalidFilesPathsChan := make(chan *InvalidFile)
+
+	go func() {
+		for path := range pathsChan {
+			isValid, validationErrors, err := val.validateResource(path)
+			if isValid {
+				validFilesPathChan <- path
+			} else {
+				invalidFilesPathsChan <- &InvalidFile{
+					Path:             path,
+					ValidationErrors: validationErrors,
+				}
+			}
+			if err != nil {
+				errorChan <- err
+			}
+		}
+
+		close(invalidFilesPathsChan)
+		close(validFilesPathChan)
+		close(errorChan)
+	}()
+
+	return validFilesPathChan, invalidFilesPathsChan, errorChan
+}
+
+func (val *K8sValidator) validateResource(filepath string) (bool, []error, error) {
+	f, err := os.Open(filepath)
+	if err != nil {
+		return false, []error{}, fmt.Errorf("failed opening %s: %s", filepath, err)
+	}
+
+	results := val.validationClient.Validate(filepath, f)
+	isValid := true
+	var validationErrors []error
+	for _, res := range results {
+
+		// A file might contain multiple resources
+		// File starts with ---, the parser assumes a first empty resource
+		if res.Status == kubeconformValidator.Invalid || res.Status == kubeconformValidator.Error {
+			isValid = false
+			validationErrors = append(validationErrors, res.Err)
+		}
+	}
+
+	return isValid, validationErrors, nil
+}
+
+func newKubconformValidator(k8sVersion string) ValidationClient {
+	v, _ := kubeconformValidator.New(nil, kubeconformValidator.Opts{Strict: true, KubernetesVersion: k8sVersion})
+	return v
+}
diff --git a/bl/versionMessage.go b/bl/versionMessage.go
deleted file mode 100644
index 45a1676..0000000
--- a/bl/versionMessage.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package bl
-
-import (
-	"github.com/datreeio/datree/pkg/cliClient"
-	"github.com/datreeio/datree/pkg/printer"
-)
-
-type VersionMessageClient interface {
-	GetVersionMessage(cliVersion string) (*cliClient.VersionMessage, error)
-}
-
-func PopulateVersionMessageChan(c VersionMessageClient, cliVersion string) <-chan *cliClient.VersionMessage {
-	messageChannel := make(chan *cliClient.VersionMessage)
-	go func() {
-		msg, _ := c.GetVersionMessage(cliVersion)
-		if msg != nil {
-			messageChannel <- msg
-		}
-		close(messageChannel)
-	}()
-
-	return messageChannel
-}
-
-func HandleVersionMessage(messageChannel <-chan *cliClient.VersionMessage) {
-	msg, ok := <-messageChannel
-	if ok {
-		p := printer.CreateNewPrinter()
-		p.PrintVersionMessage(msg.MessageText+"\n", msg.MessageColor)
-	}
-}
diff --git a/cmd/root.go b/cmd/root.go
index 180b99a..77b378c 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -1,14 +1,15 @@
 package cmd
 
 import (
-	"github.com/datreeio/datree/bl"
+	"github.com/datreeio/datree/bl/evaluation"
+	"github.com/datreeio/datree/bl/messager"
 	"github.com/datreeio/datree/cmd/test"
 	"github.com/datreeio/datree/cmd/version"
 	"github.com/datreeio/datree/pkg/cliClient"
 	"github.com/datreeio/datree/pkg/deploymentConfig"
+	"github.com/datreeio/datree/pkg/fileReader"
 	"github.com/datreeio/datree/pkg/localConfig"
 	"github.com/datreeio/datree/pkg/printer"
-	"github.com/datreeio/datree/pkg/propertiesExtractor"
 	"github.com/spf13/cobra"
 )
 
@@ -23,16 +24,19 @@ var CliVersion string
 func init() {
 	app := startup()
 
-	rootCmd.AddCommand(test.NewTestCommand(&test.TestCommandContext{
-		CliVersion:           CliVersion,
-		Evaluator:            app.Context.Evaluator,
-		LocalConfig:          app.Context.LocalConfig,
-		VersionMessageClient: app.Context.VersionMessageClient,
+	rootCmd.AddCommand(test.New(&test.TestCommandContext{
+		CliVersion:  CliVersion,
+		Evaluator:   app.context.Evaluator,
+		LocalConfig: app.context.LocalConfig,
+		Messager:    app.context.Messager,
+		Printer:     app.context.Printer,
+		Reader:      app.context.Reader,
 	}))
 
-	rootCmd.AddCommand(version.NewVersionCommand(&version.VersionCommandContext{
-		CliVersion:           CliVersion,
-		VersionMessageClient: app.Context.VersionMessageClient,
+	rootCmd.AddCommand(version.New(&version.VersionCommandContext{
+		CliVersion: CliVersion,
+		Messager:   app.context.Messager,
+		Printer:    app.context.Printer,
 	}))
 }
 
@@ -40,34 +44,32 @@ func Execute() error {
 	return rootCmd.Execute()
 }
 
+type context struct {
+	LocalConfig *localConfig.LocalConfiguration
+	Evaluator   *evaluation.Evaluator
+	CliClient   *cliClient.CliClient
+	Messager    *messager.Messager
+	Printer     *printer.Printer
+	Reader      *fileReader.FileReader
+}
+
 type app struct {
-	Context struct {
-		LocalConfig          *localConfig.LocalConfiguration
-		Evaluator            *bl.Evaluator
-		CliClient            *cliClient.CliClient
-		VersionMessageClient cliClient.VersionMessageClient
-	}
+	context *context
 }
 
 func startup() *app {
-	app := &app{
-		Context: struct {
-			LocalConfig          *localConfig.LocalConfiguration
-			Evaluator            *bl.Evaluator
-			CliClient            *cliClient.CliClient
-			VersionMessageClient cliClient.VersionMessageClient
-		}{},
-	}
-
-	client := cliClient.NewCliClient(deploymentConfig.URL)
-	versionMessageClient := cliClient.NewVersionMessageClient(deploymentConfig.URL)
-	extractor := propertiesExtractor.NewPropertiesExtractor(nil)
+	config, _ := localConfig.GetLocalConfiguration()
+	cliClient := cliClient.NewCliClient(deploymentConfig.URL)
 	printer := printer.CreateNewPrinter()
-	evaluator := bl.CreateNewEvaluator(extractor, client, printer)
-
-	app.Context.LocalConfig = &localConfig.LocalConfiguration{}
-	app.Context.Evaluator = evaluator
-	app.Context.VersionMessageClient = versionMessageClient
 
-	return app
+	return &app{
+		context: &context{
+			LocalConfig: config,
+			Evaluator:   evaluation.New(cliClient),
+			CliClient:   cliClient,
+			Messager:    messager.New(cliClient),
+			Printer:     printer,
+			Reader:      fileReader.CreateFileReader(nil),
+		},
+	}
 }
diff --git a/cmd/version/main.go b/cmd/version/main.go
index 9dfed9a..f209ce4 100644
--- a/cmd/version/main.go
+++ b/cmd/version/main.go
@@ -3,22 +3,34 @@ package version
 import (
 	"fmt"
 
-	"github.com/datreeio/datree/bl"
+	"github.com/datreeio/datree/bl/messager"
 	"github.com/spf13/cobra"
 )
 
+type Messager interface {
+	LoadVersionMessages(messages chan *messager.VersionMessage, cliVersion string)
+}
+
+type Printer interface {
+	PrintMessage(messageText string, messageColor string)
+}
 type VersionCommandContext struct {
-	CliVersion           string
-	VersionMessageClient bl.VersionMessageClient
+	CliVersion string
+	Messager   Messager
+	Printer    Printer
 }
 
 func version(ctx *VersionCommandContext) {
-	messageChannel := bl.PopulateVersionMessageChan(ctx.VersionMessageClient, ctx.CliVersion)
+	messages := make(chan *messager.VersionMessage, 1)
+	go ctx.Messager.LoadVersionMessages(messages, ctx.CliVersion)
 	fmt.Println(ctx.CliVersion)
-	bl.HandleVersionMessage(messageChannel)
+	msg, ok := <-messages
+	if ok {
+		ctx.Printer.PrintMessage(msg.MessageText+"\n", msg.MessageColor)
+	}
 }
 
-func NewVersionCommand(ctx *VersionCommandContext) *cobra.Command {
+func New(ctx *VersionCommandContext) *cobra.Command {
 	var versionCmd = &cobra.Command{
 		Use:   "version",
 		Short: "Print the version number",
diff --git a/go.mod b/go.mod
index b202560..4ddef41 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@ go 1.15
 
 require (
 	github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect
+	github.com/aws/aws-sdk-go v1.38.40
 	github.com/bmatcuk/doublestar/v2 v2.0.4
 	github.com/briandowns/spinner v1.12.0
 	github.com/fatih/color v1.10.0
@@ -17,5 +18,9 @@ require (
 	github.com/spf13/viper v1.7.1
 	github.com/stretchr/testify v1.7.0
 	github.com/tklauser/go-sysconf v0.3.5 // indirect
+	github.com/yannh/kubeconform v0.4.7
+	golang.org/x/text v0.3.3
+	golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc
+	gopkg.in/yaml.v2 v2.4.0
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
 )
diff --git a/go.sum b/go.sum
index 1eb0ce5..9d61a06 100644
--- a/go.sum
+++ b/go.sum
@@ -22,6 +22,9 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/aws/aws-sdk-go v1.38.40 h1:VVqBFV24tGgXR11tFXPjmR+0ItbnUepbuQjdmhgu3U0=
+github.com/aws/aws-sdk-go v1.38.40/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
+github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
@@ -109,6 +112,9 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
+github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
@@ -164,6 +170,7 @@ github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
@@ -219,7 +226,15 @@ github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITn
 github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA=
 github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
+github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
+github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+github.com/yannh/kubeconform v0.4.7 h1:ExAjZYd6D0WnG/Eq/IRxvTebPbARh6e6M96Pq8Xy5u0=
+github.com/yannh/kubeconform v0.4.7/go.mod h1:lhkEiaDOtSewHGGZ8iR2iiTC0CSnR7xbMEtyL4bm4rE=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
@@ -231,6 +246,7 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -263,6 +279,7 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -290,12 +307,15 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
 golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa h1:ZYxPR6aca/uhfRJyaOAtflSHjJYiktO7QnJC5ut7iY4=
 golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -314,6 +334,7 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw
 golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8=
 golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
@@ -348,6 +369,8 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -358,3 +381,5 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
+sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
diff --git a/internal/fixtures/kube/fail-30.yaml b/internal/fixtures/kube/fail-30.yaml
index 8e0fbad..7ef87da 100644
--- a/internal/fixtures/kube/fail-30.yaml
+++ b/internal/fixtures/kube/fail-30.yaml
@@ -1,4 +1,4 @@
-apiVersion: extensions/v1beta1
+apiVersion: v1
 kind: Pod
 metadata:
   name: static-web
@@ -38,7 +38,7 @@ spec:
           containerPort: 80
           protocol: TCP
 ---
-apiVersion: kubeadm.k8s.io/v1beta1
+apiVersion: apps/v1
 kind: Deployment
 metadata:
   name: rss-site
@@ -110,25 +110,21 @@ spec:
           ports:
             - containerPort: 88
 ---
-apiVersion: networking.k8s.io/v1
+apiVersion: networking.k8s.io/v1beta1
 kind: Ingress
 metadata:
-  name: ingress-wildcard-host
-  labels:
-    owner: test@datree.io
-    environment: prod
+  name: ingress-myservicea
+  annotations:
+    kubernetes.io/ingress.class: "nginx"
 spec:
   rules:
-    - host: "*"
-      http:
-        paths:
-          - pathType: Prefix
-            path: "/bar"
-            backend:
-              service:
-                name: service1
-                port:
-                  number: 80
+  - host: "*"
+    http:
+      paths:
+      - path: /
+        backend:
+          serviceName: myservicea
+          servicePort: 80
 ---
 apiVersion: v1
 kind: Service
@@ -177,6 +173,7 @@ kind: HorizontalPodAutoscaler
 metadata:
   name: php-apache
 spec:
+  maxReplicas: 10
   scaleTargetRef:
     apiVersion: apps/v1
     kind: Deployment
@@ -190,7 +187,14 @@ spec:
           averageUtilization: 50
 ---
 apiVersion: rbac.authorization.k8s.io/v1alpha1
-kind: InitConfiguration
----
-apiVersion: apps/v1beta2
-kind: Deployment
+kind: ClusterRoleBinding
+metadata:
+    name: privilegedPSP
+subjects:
+- kind: Group
+  apiVersion: rbac.authorization.k8s.io/v1alpha1
+  name: privileged
+roleRef:
+   apiGroup: rbac.authorization.k8s.io
+   kind: ClusterRole
+   name: privilegedPSP
diff --git a/internal/fixtures/kube/pass-all.yaml b/internal/fixtures/kube/pass-all.yaml
index 3ad12b0..fd55fa9 100644
--- a/internal/fixtures/kube/pass-all.yaml
+++ b/internal/fixtures/kube/pass-all.yaml
@@ -1,49 +1,3 @@
-apiVersion: v1
-kind: Deployment
-metadata:
-  name: static-web
-  namespace: test
-  labels:
-    owner: test-at-datree.io
-    role: myrole
-    env: yishay
-spec:
-  hostPID: false
-  hostIPC: false
-  hostNetwork: false
-  volumes:
-    - name: docker-sock
-      hostPath:
-        path: /var/run/docker
-  containers:
-    - name: web
-      image: aa/nginx@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2
-      securityContext:
-        runAsUser: 10001
-      livenessProbe:
-        httpGet:
-          path: /healthz
-          port: 8080
-          httpHeaders:
-          - name: Custom-Header
-            value: Awesome
-      readinessProbe:
-        tcpSocket:
-          port: 8080
-        initialDelaySeconds: 5
-        periodSeconds: 10
-      resources:
-        requests:
-          memory: "64Mi"
-          cpu: "64m"
-        limits:
-          memory: "128Mi"
-          cpu: "500m"
-      ports:
-        - name: web
-          containerPort: 80
-          protocol: TCP
----
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -126,24 +80,21 @@ spec:
           ports:
             - containerPort: 88
 ---
-apiVersion: networking.k8s.io/v1
+apiVersion: networking.k8s.io/v1beta1
 kind: Ingress
 metadata:
-  name: ingress-wildcard-host
-  labels:
-    owner: test-at-datree.io
+  name: ingress-myservicea
+  annotations:
+    kubernetes.io/ingress.class: "nginx"
 spec:
   rules:
-  - host: "*.example.com"
+  - host: myservicea.foo.org
     http:
       paths:
-      - pathType: Prefix
-        path: "/bar"
+      - path: /
         backend:
-          service:
-            name: service1
-            port:
-              number: 80
+          serviceName: myservicea
+          servicePort: 80
 ---
 apiVersion: v1
 kind: Service
diff --git a/pkg/cliClient/cliClient.go b/pkg/cliClient/cliClient.go
index 7937b94..d656cba 100644
--- a/pkg/cliClient/cliClient.go
+++ b/pkg/cliClient/cliClient.go
@@ -1,115 +1,23 @@
 package cliClient
 
 import (
-	"encoding/json"
-	"net/http"
-	"time"
-
 	"github.com/datreeio/datree/pkg/httpClient"
-	extractor "github.com/datreeio/datree/pkg/propertiesExtractor"
 )
 
 type HTTPClient interface {
 	Request(method string, resourceURI string, body interface{}, headers map[string]string) (httpClient.Response, error)
 }
 type CliClient struct {
-	baseUrl    string
-	httpClient HTTPClient
+	baseUrl       string
+	httpClient    HTTPClient
+	timeoutClient HTTPClient
 }
 
 func NewCliClient(url string) *CliClient {
 	httpClient := httpClient.NewClient(url, nil)
 	return &CliClient{
-		baseUrl:    url,
-		httpClient: httpClient,
-	}
-}
-
-type VersionMessageClient struct {
-	baseUrl    string
-	httpClient HTTPClient
-}
-
-func NewVersionMessageClient(url string) VersionMessageClient {
-	httpClient := httpClient.NewClientTimeout(url, nil, 900*time.Millisecond)
-	return VersionMessageClient{
-		baseUrl:    url,
-		httpClient: httpClient,
+		baseUrl:       url,
+		httpClient:    httpClient,
+		timeoutClient: nil,
 	}
 }
-
-type Metadata struct {
-	CliVersion      string `json:"cliVersion"`
-	Os              string `json:"os"`
-	PlatformVersion string `json:"platformVersion"`
-	KernelVersion   string `json:"kernelVersion"`
-}
-
-type EvaluationRequest struct {
-	CliId    string                     `json:"cliId"`
-	Metadata Metadata                   `json:"metadata"`
-	Files    []extractor.FileProperties `json:"files"`
-}
-
-type Match struct {
-	FileName string `json:"fileName"`
-	Path     string `json:"path"`
-	Value    string `json:"value"`
-}
-
-type EvaluationResult struct {
-	Passed  bool `json:"passed"`
-	Results struct {
-		Matches    []Match `json:"matches"`
-		Mismatches []Match `json:"mismatches"`
-	} `json:"results"`
-	Rule struct {
-		ID             int    `json:"defaultRuleId"`
-		Name           string `json:"name"`
-		Description    string `json:"description"`
-		FailSuggestion string `json:"failSuggestion"`
-	} `json:"rule"`
-}
-
-type EvaluationResponse struct {
-	EvaluationId int                `json:"evaluationId"`
-	Results      []EvaluationResult `json:"results"`
-	Status       string             `json:"status"`
-}
-
-func (c *CliClient) RequestEvaluation(request EvaluationRequest) (EvaluationResponse, error) {
-	res, err := c.httpClient.Request(http.MethodPost, "/cli/evaluate", request, nil)
-	if err != nil {
-		return EvaluationResponse{}, err
-	}
-
-	var evaluationResponse = &EvaluationResponse{}
-	err = json.Unmarshal(res.Body, &evaluationResponse)
-	if err != nil {
-		return EvaluationResponse{}, err
-	}
-
-	return *evaluationResponse, nil
-}
-
-type VersionMessage struct {
-	CliVersion   string `json:"cliVersion"`
-	MessageText  string `json:"messageText"`
-	MessageColor string `json:"messageColor"`
-}
-
-func (c VersionMessageClient) GetVersionMessage(cliVersion string) (*VersionMessage, error) {
-	res, err := c.httpClient.Request(http.MethodGet, "/cli/messages/versions/"+cliVersion, nil, nil)
-	if err != nil {
-		return nil, err
-	}
-
-	var response = &VersionMessage{}
-	err = json.Unmarshal(res.Body, &response)
-
-	if err != nil {
-		return nil, err
-	}
-
-	return response, nil
-}
diff --git a/pkg/cliClient/evaluation.go b/pkg/cliClient/evaluation.go
new file mode 100644
index 0000000..d473c57
--- /dev/null
+++ b/pkg/cliClient/evaluation.go
@@ -0,0 +1,99 @@
+package cliClient
+
+import (
+	"encoding/json"
+	"github.com/datreeio/datree/pkg/extractor"
+	"net/http"
+)
+
+type Metadata struct {
+	CliVersion      string `json:"cliVersion"`
+	Os              string `json:"os"`
+	PlatformVersion string `json:"platformVersion"`
+	KernelVersion   string `json:"kernelVersion"`
+}
+
+type CreateEvaluationRequest struct {
+	CliId      string    `json:"cliId"`
+	Metadata   *Metadata `json:"metadata"`
+	K8sVersion string    `json:"k8sVersion"`
+}
+
+type CreateEvaluationResponse struct {
+	EvaluationId int `json:"evaluationId"`
+}
+
+func (c *CliClient) CreateEvaluation(request *CreateEvaluationRequest) (int, error) {
+	httpRes, err := c.httpClient.Request(http.MethodPost, "/cli/evaluation/create", request, nil)
+	if err != nil {
+		return 0, err
+	}
+
+	var res = &CreateEvaluationResponse{}
+	err = json.Unmarshal(httpRes.Body, &res)
+	if err != nil {
+		return 0, err
+	}
+
+	return res.EvaluationId, nil
+}
+
+type Match struct {
+	FileName string `json:"fileName"`
+	Path     string `json:"path"`
+	Value    string `json:"value"`
+}
+
+type EvaluationResult struct {
+	Passed  bool `json:"passed"`
+	Results struct {
+		Matches    []*Match `json:"matches"`
+		Mismatches []*Match `json:"mismatches"`
+	} `json:"results"`
+	Rule struct {
+		ID             int    `json:"defaultRuleId"`
+		Name           string `json:"name"`
+		Description    string `json:"description"`
+		FailSuggestion string `json:"failSuggestion"`
+	} `json:"rule"`
+}
+
+type EvaluationResponse struct {
+	Results []*EvaluationResult `json:"results"`
+	Status  string              `json:"status"`
+}
+
+type EvaluationRequest struct {
+	EvaluationId int                            `json:"evaluationId"`
+	Files        []*extractor.FileConfiguration `json:"files"`
+}
+
+func (c *CliClient) RequestEvaluation(request *EvaluationRequest) (*EvaluationResponse, error) {
+	res, err := c.httpClient.Request(http.MethodPost, "/cli/evaluate", request, nil)
+	if err != nil {
+		return &EvaluationResponse{}, err
+	}
+
+	var evaluationResponse = &EvaluationResponse{}
+	err = json.Unmarshal(res.Body, &evaluationResponse)
+	if err != nil {
+		return &EvaluationResponse{}, err
+	}
+
+	return evaluationResponse, nil
+}
+
+type UpdateEvaluationValidationRequest struct {
+	EvaluationId   int       `json:"evaluationId"`
+	InvalidFiles   []*string `json:"failedFiles"`
+	StopEvaluation bool      `json:"stopEvaluation"`
+}
+
+func (c *CliClient) UpdateEvaluationValidation(request *UpdateEvaluationValidationRequest) error {
+	_, err := c.httpClient.Request(http.MethodPost, "/cli/evaluation/validation/k8s", request, nil)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/pkg/cliClient/messages.go b/pkg/cliClient/messages.go
new file mode 100644
index 0000000..c878848
--- /dev/null
+++ b/pkg/cliClient/messages.go
@@ -0,0 +1,40 @@
+package cliClient
+
+import (
+	"encoding/json"
+	"net/http"
+	"time"
+
+	"github.com/datreeio/datree/pkg/httpClient"
+)
+
+type VersionMessage struct {
+	CliVersion   string `json:"cliVersion"`
+	MessageText  string `json:"messageText"`
+	MessageColor string `json:"messageColor"`
+}
+
+func (c *CliClient) GetVersionMessage(cliVersion string, timeout int) (*VersionMessage, error) {
+	_timeout := time.Duration(timeout) * time.Millisecond
+
+	var timeoutClient HTTPClient
+	if c.timeoutClient != nil {
+		timeoutClient = c.timeoutClient
+	} else {
+		timeoutClient = httpClient.NewClientTimeout(c.baseUrl, nil, _timeout)
+	}
+
+	res, err := timeoutClient.Request(http.MethodGet, "/cli/messages/versions/"+cliVersion, nil, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	var response = &VersionMessage{}
+	err = json.Unmarshal(res.Body, &response)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return response, nil
+}
diff --git a/pkg/extractor/extractor.go b/pkg/extractor/extractor.go
new file mode 100644
index 0000000..8978587
--- /dev/null
+++ b/pkg/extractor/extractor.go
@@ -0,0 +1,74 @@
+package extractor
+
+import (
+	"bytes"
+	"io"
+	"io/ioutil"
+
+	"gopkg.in/yaml.v3"
+)
+
+type FileReader interface {
+	ReadFileContent(filepath string) (string, error)
+}
+
+type K8sConfiguration map[string]interface{}
+
+type FileConfiguration struct {
+	FileName       string             `json:"fileName"`
+	Configurations []K8sConfiguration `json:"configurations"`
+}
+
+type Error struct {
+	Message  string
+	Filename string
+}
+
+func ExtractConfiguration(path string) (*FileConfiguration, *Error) {
+	content, err := readFileContent(path)
+	if err != nil {
+		return nil, &Error{Filename: path, Message: err.Error()}
+	}
+
+	configurations, err := yamlToK8sConfigurations(content)
+	if err != nil {
+		return nil, &Error{Filename: path, Message: err.Error()}
+	} else {
+		f := &FileConfiguration{
+			Configurations: *configurations,
+			FileName:       path,
+		}
+		return f, nil
+	}
+}
+
+func yamlToK8sConfigurations(content string) (*[]K8sConfiguration, error) {
+	var configurations []K8sConfiguration
+
+	yamlDecoder := yaml.NewDecoder(bytes.NewReader([]byte(content)))
+
+	var err error
+	for {
+		var doc = map[string]interface{}{}
+		err = yamlDecoder.Decode(&doc)
+		if err != nil {
+			break
+		}
+		configurations = append(configurations, doc)
+	}
+
+	if err == io.EOF {
+		err = nil
+	}
+
+	return &configurations, err
+}
+
+func readFileContent(filepath string) (string, error) {
+	dat, err := ioutil.ReadFile(filepath)
+	if err != nil {
+		return "", err
+	}
+
+	return string(dat), nil
+}
diff --git a/pkg/fileReader/reader.go b/pkg/fileReader/reader.go
index cbf6a08..be66c2c 100644
--- a/pkg/fileReader/reader.go
+++ b/pkg/fileReader/reader.go
@@ -56,6 +56,20 @@ func CreateFileReader(opts *FileReaderOptions) *FileReader {
 	return fileReader
 }
 
+func (fr *FileReader) FilterFiles(paths []string) []string {
+	var filePaths []string
+
+	for _, path := range paths {
+		stat, _ := fr.stat(path)
+
+		if !stat.IsDir() {
+			filePaths = append(filePaths, path)
+		}
+	}
+
+	return filePaths
+}
+
 func (fr *FileReader) ReadFileContent(filepath string) (string, error) {
 	dat, err := fr.readFile(filepath)
 	if err != nil {
@@ -64,46 +78,3 @@ func (fr *FileReader) ReadFileContent(filepath string) (string, error) {
 
 	return string(dat), nil
 }
-
-func (fr *FileReader) GetFilesPaths(paths []string) (chan string, chan error) {
-	errorChan := make(chan error, 100)
-	filePathsChan := make(chan string, 100)
-
-	go func() {
-		var err error
-
-	iterateMatches:
-		for {
-			if err != nil {
-				errorChan <- err
-				break iterateMatches
-			}
-
-			for _, match := range paths {
-				file, err := fr.stat(match)
-				if err != nil {
-					errorChan <- err
-					continue
-				}
-
-				if !file.IsDir() {
-					absolutePath, err := fr.abs(match)
-					if err != nil {
-						errorChan <- err
-						continue
-					} else {
-						filePathsChan <- absolutePath
-					}
-
-				}
-			}
-
-			break
-		}
-
-		close(filePathsChan)
-		close(errorChan)
-	}()
-
-	return filePathsChan, errorChan
-}
diff --git a/pkg/localConfig/localConfig.go b/pkg/localConfig/localConfig.go
index 07aa60d..e6912e7 100644
--- a/pkg/localConfig/localConfig.go
+++ b/pkg/localConfig/localConfig.go
@@ -13,7 +13,7 @@ type LocalConfiguration struct {
 	CliId string
 }
 
-func (l *LocalConfiguration) GetConfiguration() (LocalConfiguration, error) {
+func GetLocalConfiguration() (*LocalConfiguration, error) {
 	viper.SetEnvPrefix("datree")
 	viper.AutomaticEnv()
 	token := viper.GetString("token")
@@ -21,7 +21,7 @@ func (l *LocalConfiguration) GetConfiguration() (LocalConfiguration, error) {
 	if token == "" {
 		usr, err := user.Current()
 		if err != nil {
-			return LocalConfiguration{}, err
+			return &LocalConfiguration{}, err
 		}
 
 		homedir := usr.HomeDir
@@ -58,5 +58,5 @@ func (l *LocalConfiguration) GetConfiguration() (LocalConfiguration, error) {
 		}
 	}
 
-	return LocalConfiguration{CliId: token}, nil
+	return &LocalConfiguration{CliId: token}, nil
 }
diff --git a/pkg/printer/printer.go b/pkg/printer/printer.go
index bc581dd..f140c04 100644
--- a/pkg/printer/printer.go
+++ b/pkg/printer/printer.go
@@ -19,31 +19,60 @@ func CreateNewPrinter() *Printer {
 	}
 }
 
+type WarningInfo struct {
+	Caption     string
+	Occurrences int
+	Suggestion  string
+}
+
+type ValidationInfo struct {
+	IsValid          bool
+	ValidationErrors []error
+	K8sVersion       string
+}
 type Warning struct {
-	Title   string
-	Details []struct {
-		Caption     string
-		Occurrences int
-		Suggestion  string
-	}
+	Title          string
+	Details        []WarningInfo
+	ValidationInfo ValidationInfo
 }
 
 func (p *Printer) PrintWarnings(warnings []Warning) {
 	for _, warning := range warnings {
 		p.printInColor(warning.Title, p.theme.Colors.Yellow)
-
 		fmt.Println()
 
-		for _, d := range warning.Details {
-			formattedOccurrences := fmt.Sprintf(" [%d occurrences]", d.Occurrences)
-			occurrences := p.theme.Colors.White.Sprintf(formattedOccurrences)
+		p.printInColor("[V] YAML validation\n", p.theme.Colors.Green)
 
-			caption := p.theme.Colors.Red.Sprint(d.Caption)
+		if warning.ValidationInfo.IsValid != true {
+			p.printInColor("[X] Kubernetes schema validation\n", p.theme.Colors.White)
+			fmt.Println()
 
-			fmt.Printf("%v %v %v\n", p.theme.Emoji.Error, caption, occurrences)
-			fmt.Printf("%v %v\n", p.theme.Emoji.Suggestion, d.Suggestion)
+			for _, validationError := range warning.ValidationInfo.ValidationErrors {
+				validationError := p.theme.Colors.Red.Sprint(validationError.Error())
+				fmt.Printf("%v %v\n", p.theme.Emoji.Error, validationError)
+			}
+			fmt.Println()
+			p.printInColor("[?] Policy check didn’t run for this file\n", p.theme.Colors.White)
+			fmt.Println()
+			fmt.Println()
 
+		} else {
+			p.printInColor("[V] Kubernetes schema validation\n", p.theme.Colors.Green)
 			fmt.Println()
+			p.printInColor("[X] Policy check\n", p.theme.Colors.White)
+			fmt.Println()
+
+			for _, details := range warning.Details {
+				formattedOccurrences := fmt.Sprintf(" [%d occurrences]", details.Occurrences)
+				occurrences := p.theme.Colors.White.Sprintf(formattedOccurrences)
+
+				caption := p.theme.Colors.Red.Sprint(details.Caption)
+
+				fmt.Printf("%v %v %v\n", p.theme.Emoji.Error, caption, occurrences)
+				fmt.Printf("%v %v\n", p.theme.Emoji.Suggestion, details.Suggestion)
+
+				fmt.Println()
+			}
 		}
 	}
 
@@ -62,6 +91,25 @@ type Summary struct {
 	SuccessRow SummaryItem
 }
 
+type EvaluationSummary struct {
+	FilesCount                int
+	PassedYamlValidationCount int
+	PassedK8sValidationCount  int
+	PassedPolicyCheckCount    int
+}
+
+func (p *Printer) PrintEvaluationSummary(summary EvaluationSummary) {
+	p.printInColor("(Summary)\n", p.theme.Colors.White)
+	fmt.Println()
+
+	fmt.Printf("- Passing YAML validation: %v/%v\n", summary.PassedYamlValidationCount, summary.FilesCount)
+	fmt.Println()
+	fmt.Printf("- Passing Kubernetes (1.18) schema validation: %v/%v\n", summary.PassedK8sValidationCount, summary.FilesCount)
+	fmt.Println()
+	fmt.Printf("- Passing policy check: %v/%v\n", summary.PassedPolicyCheckCount, summary.FilesCount)
+	fmt.Println()
+}
+
 func (p *Printer) PrintSummaryTable(summary Summary) {
 	summaryTable := tablewriter.NewWriter(os.Stdout)
 	summaryTable.SetAutoWrapText(false)
@@ -111,7 +159,7 @@ func (p *Printer) createNewColor(clr string) *color.Color {
 	}
 }
 
-func (p *Printer) PrintVersionMessage(messageText string, messageColor string) {
+func (p *Printer) PrintMessage(messageText string, messageColor string) {
 	colorPrintFn := p.createNewColor(messageColor)
 	p.printInColor(messageText, colorPrintFn)
 }
diff --git a/pkg/propertiesExtractor/propertiesExtractor.go b/pkg/propertiesExtractor/propertiesExtractor.go
deleted file mode 100644
index 690d3b7..0000000
--- a/pkg/propertiesExtractor/propertiesExtractor.go
+++ /dev/null
@@ -1,134 +0,0 @@
-package propertiesExtractor
-
-import (
-	"bytes"
-	"fmt"
-	"io"
-	"sync"
-
-	"github.com/datreeio/datree/pkg/fileReader"
-	"gopkg.in/yaml.v3"
-)
-
-type FileReader interface {
-	ReadFileContent(filepath string) (string, error)
-	GetFilesPaths(paths []string) (chan string, chan error)
-}
-
-type PropertiesExtractor struct {
-	reader FileReader
-}
-
-type FileError struct {
-	Message  string
-	Filename string
-}
-
-type K8sConfiguration map[string]interface{}
-
-type FileProperties struct {
-	FileName       string             `json:"fileName"`
-	Configurations []K8sConfiguration `json:"configurations"`
-}
-
-func NewPropertiesExtractor(fr FileReader) *PropertiesExtractor {
-	if fr != nil {
-		return &PropertiesExtractor{
-			reader: fr,
-		}
-	}
-	return &PropertiesExtractor{
-		reader: fileReader.CreateFileReader(nil),
-	}
-}
-
-func (e *PropertiesExtractor) ReadFilesFromPaths(paths []string, conc int) ([]*FileProperties, []FileError, []error) {
-	filePathsChan, getFilesPathsErrorsChan := e.reader.GetFilesPaths(paths)
-	filesPropertiesChan, extractPropertiesErrorsChan := e.extractFilesProperties(filePathsChan, conc)
-
-	var files []*FileProperties
-	for fileProperties := range filesPropertiesChan {
-		files = append(files, fileProperties)
-	}
-
-	fileErrors := make([]FileError, 0)
-	for fileError := range extractPropertiesErrorsChan {
-		fileErrors = append(fileErrors, fileError)
-	}
-
-	errors := make([]error, 0)
-	for err := range getFilesPathsErrorsChan {
-		errors = append(errors, fmt.Errorf("error on get file paths, err:%s", err.Error()))
-	}
-
-	return files, fileErrors, errors
-}
-
-func (e *PropertiesExtractor) extractFilesProperties(filePathsChan chan string, conc int) (chan *FileProperties, chan FileError) {
-	filesPropertiesChan := make(chan *FileProperties, 100)
-	errorsChan := make(chan FileError, 100)
-
-	wg := sync.WaitGroup{}
-	wg.Add(conc)
-
-	go func() {
-		for i := 0; i < conc; i++ {
-			go func() {
-			readFilesPathsChan:
-				for {
-					select {
-					case path, ok := <-filePathsChan:
-						if !ok {
-							break readFilesPathsChan
-						}
-						content, err := e.reader.ReadFileContent(path)
-						if err != nil {
-							errorsChan <- FileError{Filename: path, Message: err.Error()}
-							continue
-						}
-
-						configurations, err := e.yamlToK8sConfigurations(content)
-						if err != nil {
-							errorsChan <- FileError{Filename: path, Message: err.Error()}
-							continue
-						} else {
-							filesPropertiesChan <- &FileProperties{
-								Configurations: configurations,
-								FileName:       path,
-							}
-						}
-					}
-				}
-				wg.Done()
-			}()
-		}
-
-		wg.Wait()
-		close(filesPropertiesChan)
-		close(errorsChan)
-	}()
-
-	return filesPropertiesChan, errorsChan
-}
-
-func (e *PropertiesExtractor) yamlToK8sConfigurations(content string) ([]K8sConfiguration, error) {
-	var configurations []K8sConfiguration
-
-	yamlDecoder := yaml.NewDecoder(bytes.NewReader([]byte(content)))
-
-	var err error
-	for {
-		var doc K8sConfiguration
-		err = yamlDecoder.Decode(&doc)
-		if err != nil {
-			break
-		}
-		configurations = append(configurations, doc)
-	}
-
-	if err == io.EOF {
-		err = nil
-	}
-
-	return configurations, err
-}


Test patch:
diff --git a/bl/evaluation/evaluator_test.go b/bl/evaluation/evaluator_test.go
new file mode 100644
index 0000000..1ebb56b
--- /dev/null
+++ b/bl/evaluation/evaluator_test.go
@@ -0,0 +1,299 @@
+package evaluation
+
+import (
+	"github.com/datreeio/datree/bl/validation"
+	"path/filepath"
+	"testing"
+
+	"github.com/datreeio/datree/pkg/cliClient"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+)
+
+type mockCliClient struct {
+	mock.Mock
+}
+
+func (m *mockCliClient) CreateEvaluation(createEvaluationRequest *cliClient.CreateEvaluationRequest) (int, error) {
+	args := m.Called(createEvaluationRequest)
+	return args.Get(0).(int), args.Error(1)
+}
+
+func (m *mockCliClient) RequestEvaluation(evaluationRequest *cliClient.EvaluationRequest) (*cliClient.EvaluationResponse, error) {
+	args := m.Called(evaluationRequest)
+	return args.Get(0).(*cliClient.EvaluationResponse), args.Error(1)
+}
+
+func (m *mockCliClient) UpdateEvaluationValidation(request *cliClient.UpdateEvaluationValidationRequest) error {
+	args := m.Called(request)
+	return args.Error(0)
+}
+
+func (m *mockCliClient) GetVersionMessage(cliVersion string, timeout int) (*cliClient.VersionMessage, error) {
+	args := m.Called(cliVersion, timeout)
+	return args.Get(0).(*cliClient.VersionMessage), args.Error(1)
+}
+
+type cliClientMockTestCase struct {
+	createEvaluation struct {
+		evaluationId int
+		err          error
+	}
+	requestEvaluation struct {
+		response *cliClient.EvaluationResponse
+		err      error
+	}
+	updateEvaluationValidation struct {
+		err error
+	}
+	getVersionMessage struct {
+		response *cliClient.VersionMessage
+		err      error
+	}
+}
+type evaluatorMock struct {
+	cliClient *cliClientMockTestCase
+}
+
+func TestEvaluate(t *testing.T) {
+	// TODO: add actual tests
+	tests := []*evaluateTestCase{}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			mockedCliClient := &mockCliClient{}
+
+			mockedCliClient.On("CreateEvaluation", mock.Anything).Return(tt.mock.cliClient.createEvaluation.evaluationId, tt.mock.cliClient.createEvaluation.err)
+			mockedCliClient.On("UpdateEvaluationValidation", mock.Anything).Return(nil)
+
+			evaluator := &Evaluator{
+				cliClient: mockedCliClient,
+				osInfo:    tt.args.osInfo,
+			}
+
+			results, _, _, _, _ := evaluator.Evaluate(tt.args.validFilesChan, tt.args.invalidFilesChan, tt.args.evaluationId)
+
+			if tt.expected.isRequestEvaluationCalled {
+				mockedCliClient.AssertCalled(t, "RequestEvaluation", mock.Anything)
+				assert.Equal(t, tt.expected.response.Summary, results.Summary)
+				assert.Equal(t, tt.expected.response.FileNameRuleMapper, results.FileNameRuleMapper)
+			}
+
+			if tt.expected.isUpdateEvaluationValidationCalled {
+				mockedCliClient.AssertCalled(t, "UpdateEvaluationValidation", mock.Anything)
+			}
+
+		})
+	}
+}
+
+func TestCreateEvaluation(t *testing.T) {
+	tests := []*evaluateTestCase{
+		request_evaluation_all_invalid(),
+		request_evaluation_all_valid(),
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			mockedCliClient := &mockCliClient{}
+
+			mockedCliClient.On("RequestEvaluation", mock.Anything).Return(tt.mock.cliClient.requestEvaluation.response, tt.mock.cliClient.requestEvaluation.err)
+			mockedCliClient.On("UpdateEvaluationValidation", mock.Anything).Return(nil)
+
+			evaluator := &Evaluator{
+				cliClient: mockedCliClient,
+				osInfo:    tt.args.osInfo,
+			}
+
+			// TODO: define and check the rest of the values
+			results, _, _, _, _ := evaluator.Evaluate(tt.args.validFilesChan, tt.args.invalidFilesChan, tt.args.evaluationId)
+
+			if tt.expected.isRequestEvaluationCalled {
+				mockedCliClient.AssertCalled(t, "RequestEvaluation", mock.Anything)
+				assert.Equal(t, tt.expected.response.Summary, results.Summary)
+				assert.Equal(t, tt.expected.response.FileNameRuleMapper, results.FileNameRuleMapper)
+			}
+
+			if tt.expected.isUpdateEvaluationValidationCalled {
+				mockedCliClient.AssertCalled(t, "UpdateEvaluationValidation", mock.Anything)
+			}
+
+		})
+	}
+}
+
+type evaluateArgs struct {
+	validFilesChan   chan string
+	invalidFilesChan chan *validation.InvalidFile
+	evaluationId     int
+	osInfo           *OSInfo
+}
+
+type evaluateExpected struct {
+	response                           *EvaluationResults
+	errors                             []*Error
+	err                                error
+	isRequestEvaluationCalled          bool
+	isCreateEvaluationCalled           bool
+	isUpdateEvaluationValidationCalled bool
+	isGetVersionMessageCalled          bool
+}
+
+type evaluateTestCase struct {
+	name     string
+	args     *evaluateArgs
+	mock     *evaluatorMock
+	expected *evaluateExpected
+}
+
+func request_evaluation_all_valid() *evaluateTestCase {
+	invalidPath := "path/path1/service.yaml"
+
+	return &evaluateTestCase{
+		name: "should request validation without invalid files",
+		args: &evaluateArgs{
+			validFilesChan:   newFilesChan(),
+			invalidFilesChan: newInvalidFilesChan(invalidPath),
+			evaluationId:     1,
+			osInfo: &OSInfo{
+				OS:              "darwin",
+				PlatformVersion: "1.2.3",
+				KernelVersion:   "4.5.6",
+			},
+		},
+		mock: &evaluatorMock{
+			cliClient: &cliClientMockTestCase{
+				createEvaluation: struct {
+					evaluationId int
+					err          error
+				}{
+					evaluationId: 1,
+					err:          nil,
+				},
+				requestEvaluation: struct {
+					response *cliClient.EvaluationResponse
+					err      error
+				}{
+					response: &cliClient.EvaluationResponse{
+						Results: []*cliClient.EvaluationResult{},
+					},
+					err: nil,
+				},
+				updateEvaluationValidation: struct{ err error }{
+					err: nil,
+				},
+				getVersionMessage: struct {
+					response *cliClient.VersionMessage
+					err      error
+				}{
+					response: nil,
+					err:      nil,
+				},
+			},
+		},
+		expected: &evaluateExpected{
+			response: &EvaluationResults{
+				FileNameRuleMapper: make(map[string]map[int]*Rule),
+				Summary: struct {
+					RulesCount       int
+					TotalFailedRules int
+					FilesCount       int
+					TotalPassedCount int
+				}{
+					RulesCount:       0,
+					TotalFailedRules: 0,
+					FilesCount:       1,
+					TotalPassedCount: 1,
+				},
+			},
+			errors:                             []*Error{},
+			err:                                nil,
+			isRequestEvaluationCalled:          true,
+			isUpdateEvaluationValidationCalled: false,
+		},
+	}
+}
+
+func request_evaluation_all_invalid() *evaluateTestCase {
+	invalidPath := "path/path1/service.yaml"
+	return &evaluateTestCase{
+		name: "should request validation all files are invalid",
+		args: &evaluateArgs{
+			validFilesChan:   newFilesChan(),
+			invalidFilesChan: newInvalidFilesChan(invalidPath),
+			evaluationId:     1,
+			osInfo: &OSInfo{
+				OS:              "darwin",
+				PlatformVersion: "1.2.3",
+				KernelVersion:   "4.5.6",
+			},
+		},
+		mock: &evaluatorMock{
+			cliClient: &cliClientMockTestCase{
+				createEvaluation: struct {
+					evaluationId int
+					err          error
+				}{
+					evaluationId: 1,
+					err:          nil,
+				},
+				requestEvaluation: struct {
+					response *cliClient.EvaluationResponse
+					err      error
+				}{
+					response: &cliClient.EvaluationResponse{
+						Results: []*cliClient.EvaluationResult{},
+					},
+					err: nil,
+				},
+				updateEvaluationValidation: struct{ err error }{
+					err: nil,
+				},
+				getVersionMessage: struct {
+					response *cliClient.VersionMessage
+					err      error
+				}{
+					response: nil,
+					err:      nil,
+				},
+			},
+		},
+		expected: &evaluateExpected{
+			response: &EvaluationResults{
+				FileNameRuleMapper: make(map[string]map[int]*Rule),
+				Summary: struct {
+					RulesCount       int
+					TotalFailedRules int
+					FilesCount       int
+					TotalPassedCount int
+				}{
+					RulesCount:       0,
+					TotalFailedRules: 0,
+					FilesCount:       1,
+					TotalPassedCount: 1,
+				},
+			},
+			errors:                             []*Error{},
+			err:                                nil,
+			isRequestEvaluationCalled:          false,
+			isUpdateEvaluationValidationCalled: true,
+		},
+	}
+}
+
+func newFilesChan() chan string {
+	files := make(chan string, 1)
+	p, _ := filepath.Abs("../../internal/fixtures/kube/pass-all.yaml")
+	files <- p
+	close(files)
+	return files
+}
+
+func newInvalidFilesChan(invalidPath string) chan *validation.InvalidFile {
+	invalidFilesChan := make(chan *validation.InvalidFile, 1)
+
+	invalidFilesChan <- &validation.InvalidFile{
+		Path:             invalidPath,
+		ValidationErrors: []error{},
+	}
+	close(invalidFilesChan)
+	return invalidFilesChan
+}
diff --git a/bl/evaluation/printer_test.go b/bl/evaluation/printer_test.go
new file mode 100644
index 0000000..6444106
--- /dev/null
+++ b/bl/evaluation/printer_test.go
@@ -0,0 +1,64 @@
+package evaluation
+
+import (
+	"github.com/datreeio/datree/bl/validation"
+	"os"
+	"testing"
+
+	"github.com/datreeio/datree/pkg/printer"
+	"github.com/stretchr/testify/mock"
+)
+
+type mockPrinter struct {
+	mock.Mock
+}
+
+func (m *mockPrinter) PrintWarnings(warnings []printer.Warning) {
+	m.Called(warnings)
+}
+
+func (c *mockPrinter) PrintSummaryTable(summary printer.Summary) {
+	c.Called(summary)
+}
+
+type printResultsTestCaseArgs struct {
+	results           *EvaluationResults
+	invalidFiles      []*validation.InvalidFile
+	evaluationSummary printer.EvaluationSummary
+	loginURL          string
+	outputFormat      string
+	printer           Printer
+}
+
+type printResultsTestCase struct {
+	name     string
+	args     *printResultsTestCaseArgs
+	expected error
+}
+
+// TODO: write actual tests, what is this even?
+func TestPrintResults(t *testing.T) {
+	tests := []*printResultsTestCase{}
+	for _, tt := range tests {
+		mockedPrinter := &mockPrinter{}
+		mockedPrinter.On("PrintWarnings", mock.Anything)
+		mockedPrinter.On("PrintSummaryTable", mock.Anything)
+		t.Run(tt.name, func(t *testing.T) {
+			PrintResults(tt.args.results, tt.args.invalidFiles, tt.args.evaluationSummary, tt.args.loginURL, tt.args.outputFormat, tt.args.printer)
+
+			if tt.args.outputFormat == "json" {
+				mockedPrinter.AssertNotCalled(t, "PrintWarnings")
+				mockedPrinter.AssertCalled(t, "PrintSummaryTable")
+			} else if tt.args.outputFormat == "yaml" {
+				mockedPrinter.AssertNotCalled(t, "PrintWarnings")
+				mockedPrinter.AssertCalled(t, "PrintSummaryTable")
+
+			} else {
+				pwd, _ := os.Getwd()
+				warnings, _ := parseToPrinterWarnings(tt.args.results, tt.args.invalidFiles, pwd)
+				mockedPrinter.AssertCalled(t, "PrintWarnings", warnings)
+				mockedPrinter.AssertCalled(t, "PrintSummaryTable", tt.args.results, tt.args.loginURL)
+			}
+		})
+	}
+}
diff --git a/bl/evaluator_test.go b/bl/evaluator_test.go
deleted file mode 100644
index 41cf491..0000000
--- a/bl/evaluator_test.go
+++ /dev/null
@@ -1,330 +0,0 @@
-package bl
-
-import (
-	"fmt"
-	"testing"
-
-	"github.com/datreeio/datree/pkg/cliClient"
-	"github.com/datreeio/datree/pkg/printer"
-	"github.com/datreeio/datree/pkg/propertiesExtractor"
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/mock"
-)
-
-type mockPropertiesExtractor struct {
-	mock.Mock
-}
-
-func (m *mockPropertiesExtractor) ReadFilesFromPaths(paths []string, conc int) ([]*propertiesExtractor.FileProperties, []propertiesExtractor.FileError, []error) {
-	args := m.Called(paths, conc)
-	return args.Get(0).([]*propertiesExtractor.FileProperties), args.Get(1).([]propertiesExtractor.FileError), args.Get(2).([]error)
-}
-
-type mockCliClient struct {
-	mock.Mock
-}
-
-func (m *mockCliClient) RequestEvaluation(evaluationRequest cliClient.EvaluationRequest) (cliClient.EvaluationResponse, error) {
-	args := m.Called(evaluationRequest)
-	return args.Get(0).(cliClient.EvaluationResponse), args.Error(1)
-}
-
-type mockPrinter struct {
-	mock.Mock
-}
-
-func (m *mockPrinter) PrintWarnings(warnings []printer.Warning) {
-	m.Called(warnings)
-}
-
-func (c *mockPrinter) PrintSummaryTable(summary printer.Summary) {
-	c.Called(summary)
-}
-
-type propertiesExtractorMockTestCase struct {
-	readFilesFromPaths struct {
-		properties  []*propertiesExtractor.FileProperties
-		filesErrors []propertiesExtractor.FileError
-		errors      []error
-	}
-}
-
-type cliClientMockTestCase struct {
-	requestEvaluation struct {
-		response cliClient.EvaluationResponse
-		errors   error
-	}
-}
-
-type evaluateTestCase struct {
-	name string
-	args struct {
-		paths             []string
-		cliId             string
-		evaluationConc    int
-		evaluationRequest cliClient.EvaluationRequest
-	}
-	mock struct {
-		propertiesExtractor propertiesExtractorMockTestCase
-		cliClient           cliClientMockTestCase
-	}
-	expected struct {
-		response   *EvaluationResults
-		fileErrors []propertiesExtractor.FileError
-		err        error
-	}
-}
-
-func TestEvaluate(t *testing.T) {
-	tests := []*evaluateTestCase{
-		test_evaluate_failedRequest(),
-		test_evaluate_success(),
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			propertiesExtractor := &mockPropertiesExtractor{}
-			cliClient := &mockCliClient{}
-			printer := &mockPrinter{}
-
-			propertiesExtractor.On("ReadFilesFromPaths", mock.Anything, mock.Anything).Return(tt.mock.propertiesExtractor.readFilesFromPaths.properties, tt.mock.propertiesExtractor.readFilesFromPaths.filesErrors, tt.mock.propertiesExtractor.readFilesFromPaths.errors)
-			cliClient.On("RequestEvaluation", mock.Anything).Return(tt.mock.cliClient.requestEvaluation.response, tt.mock.cliClient.requestEvaluation.errors)
-
-			evaluator := &Evaluator{
-				propertiesExtractor: propertiesExtractor,
-				cliClient:           cliClient,
-				printer:             printer,
-				osInfo: &OSInfo{
-					OS:              "darwin",
-					PlatformVersion: "1.2.3",
-					KernelVersion:   "4.5.6",
-				},
-			}
-
-			actualResponse, actualFilesErrs, actualErr := evaluator.Evaluate(tt.args.paths, tt.args.cliId, tt.args.evaluationConc, "0.0.1")
-
-			propertiesExtractor.AssertCalled(t, "ReadFilesFromPaths", tt.args.paths, tt.args.evaluationConc)
-			cliClient.AssertCalled(t, "RequestEvaluation", tt.args.evaluationRequest)
-
-			assert.Equal(t, tt.expected.response, actualResponse)
-			assert.Equal(t, tt.expected.fileErrors, actualFilesErrs)
-			assert.Equal(t, tt.expected.err, actualErr)
-		})
-	}
-}
-
-func test_evaluate_success() *evaluateTestCase {
-	evaluationResponse := cliClient.EvaluationResponse{
-		EvaluationId: 1,
-		Results: []cliClient.EvaluationResult{
-			{
-				Passed: true,
-				Results: struct {
-					Matches    []cliClient.Match "json:\"matches\""
-					Mismatches []cliClient.Match "json:\"mismatches\""
-				}{
-					Matches:    []cliClient.Match{},
-					Mismatches: []cliClient.Match{},
-				},
-			},
-		},
-	}
-	return &evaluateTestCase{
-		name: "success",
-		args: struct {
-			paths             []string
-			cliId             string
-			evaluationConc    int
-			evaluationRequest cliClient.EvaluationRequest
-		}{
-			paths:          []string{"path1/path2/file.yaml"},
-			cliId:          "cliId-test",
-			evaluationConc: 1,
-			evaluationRequest: cliClient.EvaluationRequest{
-				CliId: "cliId-test",
-				Metadata: cliClient.Metadata{
-					CliVersion:      "0.0.1",
-					Os:              "darwin",
-					PlatformVersion: "1.2.3",
-					KernelVersion:   "4.5.6",
-				},
-				Files: []propertiesExtractor.FileProperties{{
-					FileName:       "path1/path2/file.yaml",
-					Configurations: []propertiesExtractor.K8sConfiguration{{"apiVersion": "extensions/v1beta1"}},
-				}},
-			}},
-		mock: struct {
-			propertiesExtractor propertiesExtractorMockTestCase
-			cliClient           cliClientMockTestCase
-		}{
-			propertiesExtractor: propertiesExtractorMockTestCase{
-				readFilesFromPaths: struct {
-					properties  []*propertiesExtractor.FileProperties
-					filesErrors []propertiesExtractor.FileError
-					errors      []error
-				}{
-					properties: []*propertiesExtractor.FileProperties{{
-						FileName:       "path1/path2/file.yaml",
-						Configurations: []propertiesExtractor.K8sConfiguration{{"apiVersion": "extensions/v1beta1"}},
-					}},
-					filesErrors: []propertiesExtractor.FileError{},
-					errors:      []error{},
-				},
-			},
-			cliClient: cliClientMockTestCase{
-				requestEvaluation: struct {
-					response cliClient.EvaluationResponse
-					errors   error
-				}{
-					response: evaluationResponse,
-					errors:   nil,
-				},
-			},
-		},
-		expected: struct {
-			response   *EvaluationResults
-			fileErrors []propertiesExtractor.FileError
-			err        error
-		}{
-			response: &EvaluationResults{
-				FileNameRuleMapper: map[string]map[int]*Rule{}, Summary: struct {
-					RulesCount       int
-					TotalFailedRules int
-					FilesCount       int
-				}{RulesCount: 1, TotalFailedRules: 0, FilesCount: 1},
-			},
-			fileErrors: []propertiesExtractor.FileError{},
-			err:        nil,
-		},
-	}
-}
-
-func test_evaluate_failedRequest() *evaluateTestCase {
-	evaluationResponse := cliClient.EvaluationResponse{}
-	return &evaluateTestCase{
-		name: "fail",
-		args: struct {
-			paths             []string
-			cliId             string
-			evaluationConc    int
-			evaluationRequest cliClient.EvaluationRequest
-		}{
-			paths:          []string{"path1/path2/file.yaml"},
-			cliId:          "cliId-test",
-			evaluationConc: 1,
-			evaluationRequest: cliClient.EvaluationRequest{
-				CliId: "cliId-test",
-				Metadata: cliClient.Metadata{
-					CliVersion:      "0.0.1",
-					Os:              "darwin",
-					PlatformVersion: "1.2.3",
-					KernelVersion:   "4.5.6",
-				},
-				Files: []propertiesExtractor.FileProperties{{
-					FileName:       "path1/path2/file.yaml",
-					Configurations: []propertiesExtractor.K8sConfiguration{{"apiVersion": "extensions/v1beta1"}},
-				}},
-			}},
-		mock: struct {
-			propertiesExtractor propertiesExtractorMockTestCase
-			cliClient           cliClientMockTestCase
-		}{
-			propertiesExtractor: propertiesExtractorMockTestCase{
-				readFilesFromPaths: struct {
-					properties  []*propertiesExtractor.FileProperties
-					filesErrors []propertiesExtractor.FileError
-					errors      []error
-				}{
-					properties: []*propertiesExtractor.FileProperties{{
-						FileName:       "path1/path2/file.yaml",
-						Configurations: []propertiesExtractor.K8sConfiguration{{"apiVersion": "extensions/v1beta1"}}}},
-					filesErrors: []propertiesExtractor.FileError{},
-					errors:      []error{},
-				},
-			},
-			cliClient: cliClientMockTestCase{
-				requestEvaluation: struct {
-					response cliClient.EvaluationResponse
-					errors   error
-				}{
-					response: evaluationResponse,
-					errors:   fmt.Errorf("error"),
-				},
-			},
-		},
-		expected: struct {
-			response   *EvaluationResults
-			fileErrors []propertiesExtractor.FileError
-			err        error
-		}{
-			response:   nil,
-			fileErrors: []propertiesExtractor.FileError{},
-			err:        fmt.Errorf("error"),
-		},
-	}
-}
-
-func TestPrintResults(t *testing.T) {
-	printerSpy := &mockPrinter{}
-
-	printerSpy.On("PrintWarnings", mock.Anything).Return()
-	printerSpy.On("PrintSummaryTable", mock.Anything).Return()
-
-	evaluator := &Evaluator{
-		propertiesExtractor: &mockPropertiesExtractor{},
-		cliClient:           &mockCliClient{},
-		printer:             printerSpy,
-	}
-
-	results := EvaluationResults{
-		FileNameRuleMapper: map[string]map[int]*Rule{},
-		Summary: struct {
-			RulesCount       int
-			TotalFailedRules int
-			FilesCount       int
-		}{},
-	}
-
-	evaluator.PrintResults(&results, "cli_id", "")
-
-	expectedPrinterWarnings := []printer.Warning{}
-	printerSpy.AssertCalled(t, "PrintWarnings", expectedPrinterWarnings)
-
-	plainRows := []printer.SummaryItem{
-		{
-			RightCol: "0",
-			LeftCol:  "Enabled rules in policy “default”",
-			RowIndex: 0,
-		},
-		{
-			RightCol: "0",
-			LeftCol:  "Configs tested against policy",
-			RowIndex: 1,
-		},
-		{
-			RightCol: "0",
-			LeftCol:  "Total rules evaluated",
-			RowIndex: 2,
-		},
-		{
-			RightCol: "https://app.datree.io/login?cliId=cli_id",
-			LeftCol:  "See all rules in policy",
-			RowIndex: 5,
-		}}
-
-	expectedPrinterSummary := printer.Summary{
-		PlainRows: plainRows,
-		ErrorRow: printer.SummaryItem{
-			LeftCol:  "Total rules failed",
-			RightCol: "0",
-			RowIndex: 3,
-		},
-		SuccessRow: printer.SummaryItem{
-			LeftCol:  "Total rules passed",
-			RightCol: "0",
-			RowIndex: 4,
-		},
-	}
-
-	printerSpy.AssertCalled(t, "PrintSummaryTable", expectedPrinterSummary)
-
-}
diff --git a/bl/files/files_test.go b/bl/files/files_test.go
new file mode 100644
index 0000000..665920b
--- /dev/null
+++ b/bl/files/files_test.go
@@ -0,0 +1,75 @@
+package files
+
+import (
+	"path/filepath"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+type toAbsolutePathsTestCase struct {
+	name string
+	args struct {
+		paths []string
+	}
+	expected struct {
+		path string
+	}
+}
+
+func TestToAbsolutePaths(t *testing.T) {
+	tests := []*toAbsolutePathsTestCase{
+		test_existed_file(),
+		test_directory_file(),
+		test_not_existed_file(),
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			pathsChan := ToAbsolutePaths(tt.args.paths)
+			assert.Equal(t, tt.expected.path, <-pathsChan)
+		})
+	}
+
+}
+
+func test_existed_file() *toAbsolutePathsTestCase {
+	p, _ := filepath.Abs("../../internal/fixtures/kube/pass-all.yaml")
+	return &toAbsolutePathsTestCase{
+		name: "existed file, should return abs path with no errors",
+		args: struct{ paths []string }{
+			paths: []string{"../../internal/fixtures/kube/pass-all.yaml"},
+		},
+		expected: struct {
+			path string
+		}{
+			path: p,
+		},
+	}
+}
+
+func test_not_existed_file() *toAbsolutePathsTestCase {
+	return &toAbsolutePathsTestCase{
+		name: "test not existed file, should return an error",
+		args: struct{ paths []string }{
+			paths: []string{"../../internal/fixtures/kube/bla.yaml"},
+		},
+		expected: struct {
+			path string
+		}{
+			path: "",
+		},
+	}
+}
+
+func test_directory_file() *toAbsolutePathsTestCase {
+	return &toAbsolutePathsTestCase{
+		args: struct{ paths []string }{
+			paths: []string{"../../internal/fixtures/kube"},
+		},
+		expected: struct {
+			path string
+		}{
+			path: "",
+		},
+	}
+}
diff --git a/bl/messager/messenger_test.go b/bl/messager/messenger_test.go
new file mode 100644
index 0000000..b911bc6
--- /dev/null
+++ b/bl/messager/messenger_test.go
@@ -0,0 +1,108 @@
+package messager
+
+import (
+	"testing"
+
+	"github.com/datreeio/datree/pkg/cliClient"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+)
+
+type mockMessagesClient struct {
+	mock.Mock
+}
+
+func (m *mockMessagesClient) GetVersionMessage(cliVersion string, timeout int) (*cliClient.VersionMessage, error) {
+	args := m.Called(cliVersion, timeout)
+	return args.Get(0).(*cliClient.VersionMessage), args.Error(1)
+}
+
+type LoadVersionMessagesTestCaseArgs struct {
+	messagesChan chan *VersionMessage
+	cliVersion   string
+}
+
+type LoadVersionMessagesTestCaseMock struct {
+	message *cliClient.VersionMessage
+	timeout int
+}
+
+type LoadVersionMessagesTestCaseExpected struct {
+	message *VersionMessage
+}
+type LoadVersionMessagesTestCase struct {
+	name     string
+	mock     *LoadVersionMessagesTestCaseMock
+	args     *LoadVersionMessagesTestCaseArgs
+	expected *LoadVersionMessagesTestCaseExpected
+}
+
+func TestLoadVersionMessages(t *testing.T) {
+	tests := []*LoadVersionMessagesTestCase{
+		load_message_success(),
+		load_message_failed(),
+	}
+	for _, tt := range tests {
+		mockedMessagesClient := &mockMessagesClient{}
+		t.Run(tt.name, func(t *testing.T) {
+			mockedMessagesClient.On("GetVersionMessage", mock.Anything, mock.Anything).Return(tt.mock.message, nil)
+			messager := &Messager{
+				defaultTimeout: tt.mock.timeout,
+				messagesClient: mockedMessagesClient,
+			}
+			messager.LoadVersionMessages(tt.args.messagesChan, tt.args.cliVersion)
+
+			msg := <-tt.args.messagesChan
+
+			if tt.mock.message != nil {
+				assert.Equal(t, tt.mock.message.CliVersion, msg.CliVersion)
+				assert.Equal(t, tt.mock.message.MessageColor, msg.MessageColor)
+				assert.Equal(t, tt.mock.message.MessageText, msg.MessageText)
+				mockedMessagesClient.AssertCalled(t, "GetVersionMessage", tt.args.cliVersion, tt.mock.timeout)
+			}
+		})
+	}
+
+}
+
+func load_message_success() *LoadVersionMessagesTestCase {
+	return &LoadVersionMessagesTestCase{
+		name: "should load mock message into channel",
+		mock: &LoadVersionMessagesTestCaseMock{
+			message: &cliClient.VersionMessage{
+				CliVersion:   "0.1.1",
+				MessageText:  "message",
+				MessageColor: "yellow",
+			},
+			timeout: 900,
+		},
+		args: &LoadVersionMessagesTestCaseArgs{
+			messagesChan: make(chan *VersionMessage),
+			cliVersion:   "0.1.2",
+		},
+		expected: &LoadVersionMessagesTestCaseExpected{
+			message: &VersionMessage{
+				CliVersion:   "0.1.1",
+				MessageText:  "message",
+				MessageColor: "yellow",
+			},
+		},
+	}
+}
+
+func load_message_failed() *LoadVersionMessagesTestCase {
+	return &LoadVersionMessagesTestCase{
+		name: "should load mock message into channel",
+		mock: &LoadVersionMessagesTestCaseMock{
+			message: nil,
+			timeout: 900,
+		},
+		args: &LoadVersionMessagesTestCaseArgs{
+			messagesChan: make(chan *VersionMessage),
+			cliVersion:   "0.1.2",
+		},
+		expected: &LoadVersionMessagesTestCaseExpected{
+			message: nil,
+		},
+	}
+}
diff --git a/bl/validation/k8sValidator_test.go b/bl/validation/k8sValidator_test.go
new file mode 100644
index 0000000..cf0ac93
--- /dev/null
+++ b/bl/validation/k8sValidator_test.go
@@ -0,0 +1,45 @@
+package validation
+
+import (
+	"io"
+	"path/filepath"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+	kubeconformValidator "github.com/yannh/kubeconform/pkg/validator"
+)
+
+type mockValidationClient struct {
+	mock.Mock
+}
+
+func (m *mockValidationClient) Validate(filename string, r io.ReadCloser) []kubeconformValidator.Result {
+	args := m.Called(filename, r)
+	return args.Get(0).([]kubeconformValidator.Result)
+}
+
+func TestValidateResources(t *testing.T) {
+	test_valid_multiple_resources(t)
+}
+
+func test_valid_multiple_resources(t *testing.T) {
+	validationClient := &mockValidationClient{}
+	validationClient.On("Validate", mock.Anything, mock.Anything).Return([]kubeconformValidator.Result{
+		{Status: kubeconformValidator.Valid},
+	})
+	k8sValidator := K8sValidator{
+		validationClient: validationClient,
+	}
+
+	path := "../../internal/fixtures/kube/pass-all.yaml"
+
+	valid, _, _ := k8sValidator.ValidateResources([]string{path})
+	expectedPath, _ := filepath.Abs(path)
+
+	for p := range valid {
+		assert.Equal(t, expectedPath, p)
+	}
+
+	// assert.Equal(t, nil)
+}
diff --git a/cmd/test/main.go b/cmd/test/main.go
index 607a564..cb8879d 100644
--- a/cmd/test/main.go
+++ b/cmd/test/main.go
@@ -2,37 +2,58 @@ package test
 
 import (
 	"fmt"
+	"github.com/datreeio/datree/pkg/extractor"
 	"time"
 
 	"github.com/briandowns/spinner"
-	"github.com/datreeio/datree/bl"
+	"github.com/datreeio/datree/bl/evaluation"
+	"github.com/datreeio/datree/bl/messager"
+	"github.com/datreeio/datree/bl/validation"
 	"github.com/datreeio/datree/pkg/localConfig"
-	"github.com/datreeio/datree/pkg/propertiesExtractor"
+	"github.com/datreeio/datree/pkg/printer"
 	"github.com/spf13/cobra"
 )
 
-type LocalConfigManager interface {
-	GetConfiguration() (localConfig.LocalConfiguration, error)
+type Evaluator interface {
+	Evaluate(validFilesPathsChan chan string, invalidFilesPaths chan *validation.InvalidFile, evaluationId int) (*evaluation.EvaluationResults, []*validation.InvalidFile, []*extractor.FileConfiguration, []*evaluation.Error, error)
+	CreateEvaluation(cliId string, cliVersion string, k8sVersion string) (int, error)
 }
 
-type Evaluator interface {
-	PrintResults(results *bl.EvaluationResults, cliId string, output string) error
-	PrintFileParsingErrors(errors []propertiesExtractor.FileError)
-	Evaluate(paths []string, cliId string, evaluationConc int, cliVersion string) (*bl.EvaluationResults, []propertiesExtractor.FileError, error)
+type Messager interface {
+	LoadVersionMessages(messages chan *messager.VersionMessage, cliVersion string)
 }
 
-type TestCommandContext struct {
-	CliVersion           string
-	LocalConfig          LocalConfigManager
-	Evaluator            Evaluator
-	VersionMessageClient bl.VersionMessageClient
+type K8sValidator interface {
+	ValidateResources(paths []string) (chan string, chan *validation.InvalidFile, chan error)
 }
 
 type TestCommandFlags struct {
-	Output string
+	Output     string
+	K8sVersion string
+}
+
+type EvaluationPrinter interface {
+	PrintWarnings(warnings []printer.Warning)
+	PrintSummaryTable(summary printer.Summary)
+	PrintMessage(messageText string, messageColor string)
+	PrintEvaluationSummary(evaluationSummary printer.EvaluationSummary)
+}
+
+type Reader interface {
+	FilterFiles(paths []string) []string
+}
+
+type TestCommandContext struct {
+	CliVersion   string
+	LocalConfig  *localConfig.LocalConfiguration
+	Evaluator    Evaluator
+	Messager     Messager
+	K8sValidator K8sValidator
+	Printer      EvaluationPrinter
+	Reader       Reader
 }
 
-func NewTestCommand(ctx *TestCommandContext) *cobra.Command {
+func New(ctx *TestCommandContext) *cobra.Command {
 	testCommand := &cobra.Command{
 		Use:   "test",
 		Short: "Execute static analysis for pattern",
@@ -44,7 +65,14 @@ func NewTestCommand(ctx *TestCommandContext) *cobra.Command {
 				return err
 			}
 
-			testCommandFlags := TestCommandFlags{Output: outputFlag}
+			k8sVersion, err := cmd.Flags().GetString("schema-version")
+			if err != nil {
+				fmt.Println(err)
+				return err
+			}
+
+			testCommandFlags := TestCommandFlags{Output: outputFlag, K8sVersion: k8sVersion}
+			ctx.K8sValidator = validation.New(k8sVersion)
 			return test(ctx, args, testCommandFlags)
 		},
 		SilenceUsage:  true,
@@ -52,42 +80,87 @@ func NewTestCommand(ctx *TestCommandContext) *cobra.Command {
 	}
 
 	testCommand.Flags().StringP("output", "o", "", "Define output format")
+	testCommand.Flags().StringP("schema-version", "s", "1.18.0", "Set kubernetes version to validate against. Defaults to 1.18.0")
 	return testCommand
 }
 
 func test(ctx *TestCommandContext, paths []string, flags TestCommandFlags) error {
-	s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
+	messages := make(chan *messager.VersionMessage, 1)
+	defer func() {
+		msg, ok := <-messages
+		if ok {
+			ctx.Printer.PrintMessage(msg.MessageText+"\n", msg.MessageColor)
+		}
+	}()
+
+	filePaths := ctx.Reader.FilterFiles(paths)
+	if len(filePaths) == 0 {
+		noFilesErr := fmt.Errorf("No files detected")
+		fmt.Println(noFilesErr.Error())
+		return noFilesErr
+	}
 
-	s.Suffix = " Loading..."
-	s.Color("cyan")
-	s.Start()
+	spinner := createSpinner(" Loading...", "cyan")
+	spinner.Start()
 
-	messageChannel := bl.PopulateVersionMessageChan(ctx.VersionMessageClient, ctx.CliVersion)
+	go ctx.Messager.LoadVersionMessages(messages, ctx.CliVersion)
 
-	config, err := ctx.LocalConfig.GetConfiguration()
+	evaluationId, err := ctx.Evaluator.CreateEvaluation(ctx.LocalConfig.CliId, ctx.CliVersion, flags.K8sVersion)
 	if err != nil {
 		fmt.Println(err.Error())
 		return err
 	}
 
-	evaluationResponse, fileParsingErrors, err := ctx.Evaluator.Evaluate(paths, config.CliId, 50, ctx.CliVersion)
-	s.Stop()
+	validFilesPathsChan, invalidFilesPathsChan, errorsChan := ctx.K8sValidator.ValidateResources(paths)
+	go func() {
+		for err := range errorsChan {
+			fmt.Println(err)
+		}
+	}()
+
+	results, invalidFiles, filesConfigurations, errors, err := ctx.Evaluator.Evaluate(validFilesPathsChan, invalidFilesPathsChan, evaluationId)
+
+	spinner.Stop()
+
+	passedPolicyCheckCount := 0
+	if results != nil {
+		passedPolicyCheckCount = results.Summary.TotalPassedCount
+	}
+
+	evaluationSummary := printer.EvaluationSummary{
+		FilesCount:                len(paths),
+		PassedYamlValidationCount: len(paths),
+		PassedK8sValidationCount:  len(filesConfigurations),
+		PassedPolicyCheckCount:    passedPolicyCheckCount,
+	}
+
+	err = evaluation.PrintResults(results, invalidFiles, evaluationSummary, fmt.Sprintf("https://app.datree.io/login?cliId=%s", ctx.LocalConfig.CliId), flags.Output, ctx.Printer)
+
+	var invocationFailedErr error = nil
 
 	if err != nil {
-		if len(fileParsingErrors) > 0 {
-			ctx.Evaluator.PrintFileParsingErrors(fileParsingErrors)
-		}
 		fmt.Println(err.Error())
-		return err
+		invocationFailedErr = err
+	} else if len(errors) > 0 {
+		printEvaluationErrors(errors)
+		invocationFailedErr = fmt.Errorf("Invocation failed")
+	} else if len(invalidFiles) > 0 || results.Summary.TotalFailedRules > 0 {
+		invocationFailedErr = fmt.Errorf("Invocation failed")
 	}
 
-	if evaluationResponse == nil {
-		err := fmt.Errorf("no response received")
-		return err
-	}
+	return invocationFailedErr
+}
 
-	err = ctx.Evaluator.PrintResults(evaluationResponse, config.CliId, flags.Output)
-	bl.HandleVersionMessage(messageChannel)
+func createSpinner(text string, color string) *spinner.Spinner {
+	s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
+	s.Suffix = text
+	s.Color(color)
+	return s
+}
 
-	return err
+func printEvaluationErrors(errors []*evaluation.Error) {
+	fmt.Println("The following files failed:")
+	for _, fileError := range errors {
+		fmt.Printf("\n\tFilename: %s\n\tError: %s\n\t---------------------", fileError.Filename, fileError.Message)
+	}
 }
diff --git a/cmd/test/main_test.go b/cmd/test/main_test.go
index a3d9648..dfe9b69 100644
--- a/cmd/test/main_test.go
+++ b/cmd/test/main_test.go
@@ -1,108 +1,194 @@
 package test
 
 import (
+	"github.com/datreeio/datree/bl/validation"
+	"github.com/datreeio/datree/pkg/extractor"
+	"github.com/datreeio/datree/pkg/printer"
 	"testing"
 
-	"github.com/datreeio/datree/bl"
-	"github.com/datreeio/datree/pkg/cliClient"
+	"github.com/datreeio/datree/bl/evaluation"
+	"github.com/datreeio/datree/bl/messager"
 	"github.com/datreeio/datree/pkg/localConfig"
-	"github.com/datreeio/datree/pkg/propertiesExtractor"
 	"github.com/stretchr/testify/mock"
 )
 
-type mockLocalConfigManager struct {
+type mockEvaluator struct {
 	mock.Mock
 }
 
-func (m *mockLocalConfigManager) GetConfiguration() (localConfig.LocalConfiguration, error) {
-	args := m.Called()
-	return args.Get(0).(localConfig.LocalConfiguration), args.Error(1)
+func (m *mockEvaluator) Evaluate(validFilesPathsChan chan string, invalidFilesPaths chan *validation.InvalidFile, evaluationId int) (*evaluation.EvaluationResults, []*validation.InvalidFile, []*extractor.FileConfiguration, []*evaluation.Error, error) {
+	args := m.Called(validFilesPathsChan, invalidFilesPaths, evaluationId)
+	return args.Get(0).(*evaluation.EvaluationResults), args.Get(1).([]*validation.InvalidFile), args.Get(2).([]*extractor.FileConfiguration), args.Get(3).([]*evaluation.Error), args.Error(4)
 }
 
-type mockEvaluator struct {
+func (m *mockEvaluator) CreateEvaluation(cliId string, cliVersion string, k8sVersion string) (int, error) {
+	args := m.Called(cliId, cliVersion, k8sVersion)
+	return args.Get(0).(int), args.Error(1)
+}
+
+type mockMessager struct {
 	mock.Mock
 }
 
-func (m *mockEvaluator) PrintResults(results *bl.EvaluationResults, cliId string, output string) error {
-	m.Called(results, cliId, output)
-	return nil
+func (m *mockMessager) LoadVersionMessages(messages chan *messager.VersionMessage, cliVersion string) {
+	go func() {
+		messages <- &messager.VersionMessage{
+			CliVersion:   "1.2.3",
+			MessageText:  "version message mock",
+			MessageColor: "green"}
+		close(messages)
+	}()
+
+	m.Called(messages, cliVersion)
+}
+
+func (m *mockMessager) HandleVersionMessage(messageChannel <-chan *messager.VersionMessage) {
+	m.Called(messageChannel)
 }
 
-func (m *mockEvaluator) PrintFileParsingErrors(errors []propertiesExtractor.FileError) {
-	m.Called(errors)
+type K8sValidatorMock struct {
+	mock.Mock
 }
 
-func (m *mockEvaluator) Evaluate(paths []string, cliId string, evaluationConc int, cliVersion string) (*bl.EvaluationResults, []propertiesExtractor.FileError, error) {
-	args := m.Called(paths, cliId, evaluationConc)
-	return args.Get(0).(*bl.EvaluationResults), args.Get(1).([]propertiesExtractor.FileError), args.Error(2)
+func (kv *K8sValidatorMock) ValidateResources(paths []string) (chan string, chan *validation.InvalidFile, chan error) {
+	args := kv.Called(paths)
+	return args.Get(0).(chan string), args.Get(1).(chan *validation.InvalidFile), args.Get(2).(chan error)
 }
 
-type mockVersionMessageClient struct {
+type PrinterMock struct {
 	mock.Mock
 }
 
-func (m *mockVersionMessageClient) GetVersionMessage(cliVersion string) (*cliClient.VersionMessage, error) {
-	args := m.Called(cliVersion)
-	return args.Get(0).(*cliClient.VersionMessage), nil
+func (p *PrinterMock) PrintWarnings(warnings []printer.Warning) {
+	p.Called(warnings)
+}
+
+func (p *PrinterMock) PrintSummaryTable(summary printer.Summary) {
+	p.Called(summary)
 }
+
+func (p *PrinterMock) PrintMessage(messageText string, messageColor string) {
+	p.Called(messageText, messageColor)
+}
+
+func (p *PrinterMock) PrintEvaluationSummary(evaluationSummary printer.EvaluationSummary) {
+	p.Called(evaluationSummary)
+}
+
+type ReaderMock struct {
+	mock.Mock
+}
+
+func (rm *ReaderMock) FilterFiles(paths []string) []string {
+	args := rm.Called(paths)
+	return args.Get(0).([]string)
+}
+
 func TestTestCommand(t *testing.T) {
-	evaluator := &mockEvaluator{}
-	mockedEvaluateResponse := &bl.EvaluationResults{
-		FileNameRuleMapper: map[string]map[int]*bl.Rule{}, Summary: struct {
+	evaluationId := 444
+
+	evaluationResults := &evaluation.EvaluationResults{
+		FileNameRuleMapper: map[string]map[int]*evaluation.Rule{}, Summary: struct {
 			RulesCount       int
 			TotalFailedRules int
 			FilesCount       int
-		}{RulesCount: 1, TotalFailedRules: 0, FilesCount: 0},
+			TotalPassedCount int
+		}{RulesCount: 1, TotalFailedRules: 0, FilesCount: 0, TotalPassedCount: 1},
 	}
-	evaluator.On("Evaluate", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(mockedEvaluateResponse, []propertiesExtractor.FileError{}, nil)
-	evaluator.On("PrintFileParsingErrors", mock.Anything).Return()
-	evaluator.On("PrintResults", mock.Anything, mock.Anything, mock.Anything).Return()
 
-	localConfigManager := &mockLocalConfigManager{}
-	localConfigManager.On("GetConfiguration").Return(localConfig.LocalConfiguration{CliId: "134kh"}, nil)
+	invalidFiles := []*validation.InvalidFile{}
+	var fileConfigurations []*extractor.FileConfiguration
+	var evaluationErrors []*evaluation.Error
 
-	versionMessageClient := &mockVersionMessageClient{}
-	versionMessageClient.On("GetVersionMessage", mock.Anything).Return(
-		&cliClient.VersionMessage{
-			CliVersion:   "1.2.3",
-			MessageText:  "version message mock",
-			MessageColor: "green"},
-	)
+	mockedEvaluator := &mockEvaluator{}
+	mockedEvaluator.On("Evaluate", mock.Anything, mock.Anything, mock.Anything).Return(evaluationResults, invalidFiles, fileConfigurations, evaluationErrors, nil)
+	mockedEvaluator.On("CreateEvaluation", mock.Anything, mock.Anything, mock.Anything).Return(evaluationId, nil)
+
+	messager := &mockMessager{}
+	messager.On("LoadVersionMessages", mock.Anything, mock.Anything)
+
+	k8sValidatorMock := &K8sValidatorMock{}
+
+	validFilesPathChan := newValidFilesPathsChan()
+	invalidFilesChan := newInvalidFilesChan()
+
+	k8sValidatorMock.On("ValidateResources", mock.Anything).Return(validFilesPathChan, invalidFilesChan, newErrorsChan())
+
+	printerMock := &PrinterMock{}
+	printerMock.On("PrintWarnings", mock.Anything)
+	printerMock.On("PrintSummaryTable", mock.Anything)
+	printerMock.On("PrintMessage", mock.Anything, mock.Anything)
+	printerMock.On("PrintEvaluationSummary", mock.Anything)
+
+	readerMock := &ReaderMock{}
+
+	readerMock.On("FilterFiles", mock.Anything).Return([]string{"file/path"})
 
 	ctx := &TestCommandContext{
-		Evaluator:            evaluator,
-		LocalConfig:          localConfigManager,
-		VersionMessageClient: versionMessageClient,
+		K8sValidator: k8sValidatorMock,
+		Evaluator:    mockedEvaluator,
+		LocalConfig:  &localConfig.LocalConfiguration{CliId: "134kh"},
+		Messager:     messager,
+		Printer:      printerMock,
+		Reader:       readerMock,
 	}
 
-	test_testCommand_no_flags(t, localConfigManager, evaluator, mockedEvaluateResponse, ctx)
-	test_testCommand_json_output(t, localConfigManager, evaluator, mockedEvaluateResponse, ctx)
-	test_testCommand_yaml_output(t, localConfigManager, evaluator, mockedEvaluateResponse, ctx)
+	test_testCommand_no_flags(t, mockedEvaluator, validFilesPathChan, invalidFilesChan, evaluationId, ctx)
+	test_testCommand_json_output(t, mockedEvaluator, validFilesPathChan, invalidFilesChan, evaluationId, ctx)
+	test_testCommand_yaml_output(t, mockedEvaluator, validFilesPathChan, invalidFilesChan, evaluationId, ctx)
 }
 
-func test_testCommand_no_flags(t *testing.T, localConfigManager *mockLocalConfigManager, evaluator *mockEvaluator, mockedEvaluateResponse *bl.EvaluationResults, ctx *TestCommandContext) {
+func test_testCommand_no_flags(t *testing.T, evaluator *mockEvaluator, validFilesPathChan chan string, invalidFilesChan chan *validation.InvalidFile, evaluationId int, ctx *TestCommandContext) {
 	test(ctx, []string{"8/*"}, TestCommandFlags{})
-	localConfigManager.AssertCalled(t, "GetConfiguration")
 
-	evaluator.AssertCalled(t, "Evaluate", []string{"8/*"}, "134kh", 50)
+	evaluator.AssertCalled(t, "Evaluate", validFilesPathChan, invalidFilesChan, evaluationId)
 	evaluator.AssertNotCalled(t, "PrintFileParsingErrors")
-	evaluator.AssertCalled(t, "PrintResults", mockedEvaluateResponse, "134kh", "")
 }
 
-func test_testCommand_json_output(t *testing.T, localConfigManager *mockLocalConfigManager, evaluator *mockEvaluator, mockedEvaluateResponse *bl.EvaluationResults, ctx *TestCommandContext) {
+func test_testCommand_json_output(t *testing.T, evaluator *mockEvaluator, validFilesPathChan chan string, invalidFilesChan chan *validation.InvalidFile, evaluationId int, ctx *TestCommandContext) {
 	test(ctx, []string{"8/*"}, TestCommandFlags{Output: "json"})
-	localConfigManager.AssertCalled(t, "GetConfiguration")
 
-	evaluator.AssertCalled(t, "Evaluate", []string{"8/*"}, "134kh", 50)
+	evaluator.AssertCalled(t, "Evaluate", validFilesPathChan, invalidFilesChan, evaluationId)
 	evaluator.AssertNotCalled(t, "PrintFileParsingErrors")
-	evaluator.AssertCalled(t, "PrintResults", mockedEvaluateResponse, "134kh", "json")
 }
 
-func test_testCommand_yaml_output(t *testing.T, localConfigManager *mockLocalConfigManager, evaluator *mockEvaluator, mockedEvaluateResponse *bl.EvaluationResults, ctx *TestCommandContext) {
+func test_testCommand_yaml_output(t *testing.T, evaluator *mockEvaluator, validFilesPathChan chan string, invalidFilesChan chan *validation.InvalidFile, evaluationId int, ctx *TestCommandContext) {
 	test(ctx, []string{"8/*"}, TestCommandFlags{Output: "yaml"})
-	localConfigManager.AssertCalled(t, "GetConfiguration")
 
-	evaluator.AssertCalled(t, "Evaluate", []string{"8/*"}, "134kh", 50)
+	evaluator.AssertCalled(t, "Evaluate", validFilesPathChan, invalidFilesChan, evaluationId)
 	evaluator.AssertNotCalled(t, "PrintFileParsingErrors")
-	evaluator.AssertCalled(t, "PrintResults", mockedEvaluateResponse, "134kh", "yaml")
+}
+
+func newValidFilesPathsChan() chan string {
+	validFilesChan := make(chan string, 1)
+
+	go func() {
+		validFilesChan <- "valid/path"
+		close(validFilesChan)
+	}()
+
+	return validFilesChan
+}
+
+func newInvalidFilesChan() chan *validation.InvalidFile {
+	invalidFilesChan := make(chan *validation.InvalidFile, 1)
+
+	invalidFile := &validation.InvalidFile{
+		Path:             "invalid/path",
+		ValidationErrors: []error{},
+	}
+
+	go func() {
+		invalidFilesChan <- invalidFile
+		close(invalidFilesChan)
+	}()
+
+	return invalidFilesChan
+}
+
+func newErrorsChan() chan error {
+	invalidFilesChan := make(chan error, 1)
+
+	close(invalidFilesChan)
+	return invalidFilesChan
 }
diff --git a/cmd/version/main_test.go b/cmd/version/main_test.go
index 17deb2d..ba9d67d 100644
--- a/cmd/version/main_test.go
+++ b/cmd/version/main_test.go
@@ -5,32 +5,49 @@ import (
 	"io/ioutil"
 	"testing"
 
-	"github.com/datreeio/datree/pkg/cliClient"
+	"github.com/datreeio/datree/bl/messager"
 	"github.com/stretchr/testify/mock"
 )
 
-type mockVersionMessageClient struct {
+type printerMock struct {
 	mock.Mock
 }
 
-func (m *mockVersionMessageClient) GetVersionMessage(cliVersion string) (*cliClient.VersionMessage, error) {
-	args := m.Called(cliVersion)
-	return args.Get(0).(*cliClient.VersionMessage), nil
+type mockMessager struct {
+	mock.Mock
+}
+
+func (p *printerMock) PrintMessage(messageText string, messageColor string) {
+	p.Called(messageText, messageColor)
+}
+
+func (m *mockMessager) LoadVersionMessages(messages chan *messager.VersionMessage, cliVersion string) {
+	go func() {
+		messages <- &messager.VersionMessage{
+			CliVersion:   "0.0.1",
+			MessageText:  "message text",
+			MessageColor: "White",
+		}
+
+		close(messages)
+	}()
+
+	m.Called(messages, cliVersion)
 }
 
 func Test_ExecuteCommand(t *testing.T) {
-	versionMessageClient := &mockVersionMessageClient{}
-	versionMessageClient.On("GetVersionMessage", mock.Anything).Return(
-		&cliClient.VersionMessage{
-			CliVersion:   "1.2.3",
-			MessageText:  "version message mock",
-			MessageColor: "green"},
-	)
-
-	cmd := NewVersionCommand(&VersionCommandContext{
-		CliVersion:           "1.2.3",
-		VersionMessageClient: versionMessageClient,
+	messager := &mockMessager{}
+	messager.On("LoadVersionMessages", mock.Anything, mock.Anything).Return(nil)
+
+	printer := &printerMock{}
+	printer.On("PrintMessage", mock.Anything, mock.Anything).Return(nil)
+
+	cmd := New(&VersionCommandContext{
+		CliVersion: "1.2.3",
+		Messager:   messager,
+		Printer: printer,
 	})
+
 	b := bytes.NewBufferString("1.2.3")
 	cmd.SetOut(b)
 	cmd.Execute()
diff --git a/pkg/cliClient/cliClient_test.go b/pkg/cliClient/cliClient_test.go
index 451e100..27d750a 100644
--- a/pkg/cliClient/cliClient_test.go
+++ b/pkg/cliClient/cliClient_test.go
@@ -1,14 +1,16 @@
 package cliClient
 
 import (
+	"bytes"
 	"encoding/json"
+	"gopkg.in/yaml.v3"
 	"io/ioutil"
 	"net/http"
 	"path/filepath"
 	"testing"
 
+	"github.com/datreeio/datree/pkg/extractor"
 	"github.com/datreeio/datree/pkg/httpClient"
-	extractor "github.com/datreeio/datree/pkg/propertiesExtractor"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
 )
@@ -23,6 +25,10 @@ func (c *mockHTTPClient) Request(method string, resourceURI string, body interfa
 	return args.Get(0).(httpClient.Response), args.Error(1)
 }
 
+func (c *mockHTTPClient) name()  {
+
+}
+
 type RequestEvaluationTestCase struct {
 	name string
 	args struct {
@@ -45,6 +51,28 @@ type RequestEvaluationTestCase struct {
 	}
 }
 
+type CreateEvaluationTestCase struct {
+	name string
+	args struct {
+		createEvaluationRequest *CreateEvaluationRequest
+	}
+	mock struct {
+		response struct {
+			status int
+			body   *CreateEvaluationResponse
+		}
+	}
+	expected struct {
+		request struct {
+			method  string
+			uri     string
+			body    *CreateEvaluationRequest
+			headers map[string]string
+		}
+		response *CreateEvaluationResponse
+	}
+}
+
 type GetVersionMessageTestCase struct {
 	name string
 	args struct {
@@ -85,10 +113,37 @@ func TestRequestEvaluation(t *testing.T) {
 				httpClient: &httpClientMock,
 			}
 
-			res, _ := client.RequestEvaluation(*tt.args.evaluationRequest)
+			res, _ := client.RequestEvaluation(tt.args.evaluationRequest)
+
+			httpClientMock.AssertCalled(t, "Request", tt.expected.request.method, tt.expected.request.uri, tt.expected.request.body, tt.expected.request.headers)
+			assert.Equal(t, tt.expected.response, res)
+
+		})
+	}
+}
+
+func TestCreateRequestEvaluation(t *testing.T) {
+	tests := []*CreateEvaluationTestCase{
+		test_createEvaluation_success(),
+	}
+
+	httpClientMock := mockHTTPClient{}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			body, _ := json.Marshal(tt.mock.response.body)
+			mockedHTTPResponse := httpClient.Response{StatusCode: tt.mock.response.status, Body: body}
+			httpClientMock.On("Request", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(mockedHTTPResponse, nil)
+
+			client := &CliClient{
+				baseUrl:    "http://cli-service.test.io",
+				httpClient: &httpClientMock,
+			}
+
+			actualEvaluationId, _ := client.CreateEvaluation(tt.args.createEvaluationRequest)
 
-			httpClientMock.AssertCalled(t, "Request", tt.expected.request.method, tt.expected.request.uri, *tt.expected.request.body, tt.expected.request.headers)
-			assert.Equal(t, *tt.expected.response, res)
+			httpClientMock.AssertCalled(t, "Request", tt.expected.request.method, tt.expected.request.uri, tt.expected.request.body, tt.expected.request.headers)
+			assert.Equal(t, tt.expected.response.EvaluationId, actualEvaluationId)
 
 		})
 	}
@@ -106,12 +161,13 @@ func TestGetVersionMessage(t *testing.T) {
 			mockedHTTPResponse := httpClient.Response{StatusCode: tt.mock.response.status, Body: body}
 			httpClientMock.On("Request", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(mockedHTTPResponse, nil)
 
-			client := &VersionMessageClient{
+
+			client := &CliClient{
 				baseUrl:    "http://cli-service.test.io",
-				httpClient: &httpClientMock,
+				timeoutClient: &httpClientMock,
 			}
 
-			res, _ := client.GetVersionMessage(tt.args.cliVersion)
+			res, _ := client.GetVersionMessage(tt.args.cliVersion, 1000)
 			httpClientMock.AssertCalled(t, "Request", tt.expected.request.method, tt.expected.request.uri, tt.expected.request.body, tt.expected.request.headers)
 			assert.Equal(t, tt.expected.response, res)
 
@@ -119,45 +175,42 @@ func TestGetVersionMessage(t *testing.T) {
 	}
 }
 
-func readMock(path string, data interface{}) error {
+func readMock(path string) ([]extractor.K8sConfiguration, error) {
+	var configurations []extractor.K8sConfiguration
+
 	absPath, _ := filepath.Abs(path)
-	f, err := ioutil.ReadFile(absPath)
+	content, err := ioutil.ReadFile(absPath)
 
 	if err != nil {
-		return err
+		return []extractor.K8sConfiguration{}, err
 	}
 
-	err = json.Unmarshal(f, data)
-	if err != nil {
-		return err
+	yamlDecoder := yaml.NewDecoder(bytes.NewReader(content))
+
+	for {
+		var doc = map[string]interface{}{}
+		err = yamlDecoder.Decode(&doc)
+		if err != nil {
+			break
+		}
+		configurations = append(configurations, doc)
 	}
-	return nil
+
+	return configurations, nil
 }
 
-func castPropertiesMock(fileName string, path string) []extractor.FileProperties {
-	var fileProperties map[string]interface{}
-	_ = readMock(path, &fileProperties)
+func castPropertiesMock(fileName string, path string) []*extractor.FileConfiguration {
+	configurations, _ := readMock(path)
 
-	properties := []extractor.FileProperties{
+	properties := []*extractor.FileConfiguration{
 		{
 			FileName:       fileName,
-			Configurations: []extractor.K8sConfiguration{fileProperties},
+			Configurations: configurations,
 		}}
 
 	return properties
 }
 
-func castPropertiesPointersMock(fileName string, path string) []*extractor.FileProperties {
-	var filesProperties []*extractor.FileProperties
-	props := castPropertiesMock("service_mock", "mocks/service_mock.yaml")
-	for _, p := range props {
-		filesProperties = append(filesProperties, &p)
-	}
-
-	return filesProperties
-
-}
-
 func test_getVersionMessage_success() *GetVersionMessageTestCase {
 	return &GetVersionMessageTestCase{
 		name: "success - get version message",
@@ -204,6 +257,7 @@ func test_getVersionMessage_success() *GetVersionMessageTestCase {
 		},
 	}
 }
+
 func test_requestEvaluation_success() *RequestEvaluationTestCase {
 	return &RequestEvaluationTestCase{
 		name: "success - request evaluation",
@@ -211,14 +265,8 @@ func test_requestEvaluation_success() *RequestEvaluationTestCase {
 			evaluationRequest *EvaluationRequest
 		}{
 			evaluationRequest: &EvaluationRequest{
-				CliId: "cli-id-test",
-				Files: castPropertiesMock("service_mock", "mocks/service_mock.yaml"),
-				Metadata: Metadata{
-					CliVersion:      "0.0.1",
-					Os:              "darwin",
-					PlatformVersion: "1.2.3",
-					KernelVersion:   "4.5.6",
-				},
+				EvaluationId: 321,
+				Files:        castPropertiesMock("service_mock", "mocks/service_mock.yaml"),
 			},
 		},
 		mock: struct {
@@ -253,9 +301,68 @@ func test_requestEvaluation_success() *RequestEvaluationTestCase {
 				method: http.MethodPost,
 				uri:    "/cli/evaluate",
 				body: &EvaluationRequest{
-					CliId: "cli-id-test",
-					Files: castPropertiesMock("service_mock", "mocks/service_mock.yaml"),
-					Metadata: Metadata{
+					EvaluationId: 321,
+					Files:        castPropertiesMock("service_mock", "mocks/service_mock.yaml"),
+				},
+				headers: nil,
+			},
+			response: &EvaluationResponse{},
+		},
+	}
+}
+
+func test_createEvaluation_success() *CreateEvaluationTestCase {
+	return &CreateEvaluationTestCase{
+		name: "success - create evaluation",
+		args: struct {
+			createEvaluationRequest *CreateEvaluationRequest
+		}{
+			createEvaluationRequest: &CreateEvaluationRequest{
+				CliId: "cli_id",
+				Metadata: &Metadata{
+					CliVersion:      "0.0.1",
+					Os:              "darwin",
+					PlatformVersion: "1.2.3",
+					KernelVersion:   "4.5.6",
+				},
+			},
+		},
+		mock: struct {
+			response struct {
+				status int
+				body   *CreateEvaluationResponse
+			}
+		}{
+			response: struct {
+				status int
+				body   *CreateEvaluationResponse
+			}{
+				status: http.StatusOK,
+				body: &CreateEvaluationResponse{
+					EvaluationId: 123,
+				},
+			},
+		},
+		expected: struct {
+			request struct {
+				method  string
+				uri     string
+				body    *CreateEvaluationRequest
+				headers map[string]string
+			}
+			response *CreateEvaluationResponse
+		}{
+			request: struct {
+				method  string
+				uri     string
+				body    *CreateEvaluationRequest
+				headers map[string]string
+			}{
+				method: http.MethodPost,
+				uri:    "/cli/evaluation/create",
+				body: &CreateEvaluationRequest{
+					CliId: "cli_id",
+					Metadata: &Metadata{
 						CliVersion:      "0.0.1",
 						Os:              "darwin",
 						PlatformVersion: "1.2.3",
@@ -264,7 +371,9 @@ func test_requestEvaluation_success() *RequestEvaluationTestCase {
 				},
 				headers: nil,
 			},
-			response: &EvaluationResponse{},
+			response: &CreateEvaluationResponse{
+				EvaluationId: 123,
+			},
 		},
 	}
 }
diff --git a/pkg/fileReader/reader_test.go b/pkg/fileReader/reader_test.go
index e4d752c..3a9cab2 100644
--- a/pkg/fileReader/reader_test.go
+++ b/pkg/fileReader/reader_test.go
@@ -168,55 +168,6 @@ type getFilesPathsTestCase struct {
 	}
 }
 
-func TestGetFilesPaths(t *testing.T) {
-	tests := []getFilesPathsTestCase{
-		getFilesPaths_noMatchesTestCase(),
-		getFilesPaths_withMatchesTestCase(),
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			glob := globMock{}
-			stat := statMock{}
-			abs := absMock{}
-
-			abs.On("Abs", mock.Anything).Return(tt.mock.abs.response, tt.mock.abs.err)
-			stat.On("Stat", mock.Anything).Return(tt.mock.stat.response, tt.mock.stat.err)
-
-			fileReader := &FileReader{
-				glob:     glob.Glob,
-				readFile: nil,
-				stat:     stat.Stat,
-				abs:      abs.Abs,
-			}
-
-			res, err := fileReader.GetFilesPaths(tt.args.paths)
-
-			var actualRes []string
-			for m := range res {
-				actualRes = append(actualRes, m)
-			}
-
-			var actualErrs []error
-			for e := range err {
-				actualErrs = append(actualErrs, e)
-			}
-
-			assert.Equal(t, tt.expected.response, actualRes)
-			assert.Equal(t, tt.expected.err, actualErrs)
-
-			if tt.expected.abs.isCalled {
-				abs.AssertCalled(t, "Abs", tt.expected.abs.calledWith)
-			}
-
-			if tt.expected.stat.isCalled {
-				stat.AssertCalled(t, "Stat", tt.expected.stat.calledWith)
-			}
-
-		})
-	}
-}
-
 func getFilesPaths_noMatchesTestCase() getFilesPathsTestCase {
 	return getFilesPathsTestCase{
 		name: "success no matches",
diff --git a/pkg/propertiesExtractor/propertiesExtractor_test.go b/pkg/propertiesExtractor/propertiesExtractor_test.go
deleted file mode 100644
index f3be7b2..0000000
--- a/pkg/propertiesExtractor/propertiesExtractor_test.go
+++ /dev/null
@@ -1,280 +0,0 @@
-package propertiesExtractor
-
-import (
-	"fmt"
-	"reflect"
-	"testing"
-
-	"github.com/datreeio/datree/pkg/fileReader"
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/mock"
-)
-
-type mockFileReader struct {
-	mock.Mock
-}
-
-func (m *mockFileReader) ReadFileContent(filepath string) (string, error) {
-	args := m.Called(filepath)
-	return args.Get(0).(string), args.Error(1)
-}
-
-func (m *mockFileReader) GetFilesPaths(paths []string) (chan string, chan error) {
-	args := m.Called(paths)
-	return args.Get(0).(chan string), args.Get(1).(chan error)
-}
-
-type newPropertiesExtractorTestCase struct {
-	name     string
-	arg      FileReader
-	expected FileReader
-}
-
-func TestNewPropertiesExtractor(t *testing.T) {
-	expectedFileReader := &fileReader.FileReader{}
-
-	tests := []newPropertiesExtractorTestCase{
-		{
-			name:     "override with argument",
-			arg:      expectedFileReader,
-			expected: expectedFileReader,
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			propertiesExtractor := NewPropertiesExtractor(tt.arg)
-			expectedGlobFnValue := reflect.ValueOf(tt.expected)
-			actualGlobFnValue := reflect.ValueOf(propertiesExtractor.reader)
-
-			assert.Equal(t, expectedGlobFnValue.Pointer(), actualGlobFnValue.Pointer())
-		})
-	}
-}
-
-type readFilesFromPatternTestCase struct {
-	name string
-	args struct {
-		conc  int
-		paths []string
-	}
-	mock struct {
-		readFileContentResponse struct {
-			data   string
-			errors error
-		}
-		getFilesPathsResponse struct {
-			data   chan string
-			errors chan error
-		}
-	}
-	expected struct {
-		readFileContentCalledWith []string
-		filesProperties           []*FileProperties
-		fileErrors                []FileError
-		errors                    []error
-	}
-}
-
-func TestReadFilesFromPattern(t *testing.T) {
-	tests := []readFilesFromPatternTestCase{
-		test_readFileFromPattern_success(),
-		test_readFileFromPattern_invalidYaml(),
-		test_readFileFromPattern_invalidFile(),
-	}
-
-	fileReader := &mockFileReader{}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			fileReader.On("GetFilesPaths", mock.Anything).Return(tt.mock.getFilesPathsResponse.data, tt.mock.getFilesPathsResponse.errors)
-			fileReader.On("ReadFileContent", mock.Anything).Return(tt.mock.readFileContentResponse.data, tt.mock.readFileContentResponse.errors)
-
-			propertiesExtractor := &PropertiesExtractor{
-				reader: fileReader,
-			}
-			actualFilesProperties, actualFileErrs, actualErrors := propertiesExtractor.ReadFilesFromPaths(tt.args.paths, tt.args.conc)
-
-			fileReader.AssertCalled(t, "GetFilesPaths", tt.args.paths)
-			fileReader.AssertCalled(t, "ReadFileContent", tt.expected.readFileContentCalledWith[0])
-
-			for i, prop := range actualFilesProperties {
-				assert.Equal(t, tt.expected.filesProperties[i], prop)
-			}
-
-			for i, err := range actualFileErrs {
-				assert.Equal(t, tt.expected.fileErrors[i], err)
-			}
-
-			for i, err := range actualErrors {
-				assert.Equal(t, tt.expected.errors[i], err)
-			}
-		})
-	}
-}
-
-func test_readFileFromPattern_success() readFilesFromPatternTestCase {
-	return readFilesFromPatternTestCase{
-		name: "success",
-		args: struct {
-			conc  int
-			paths []string
-		}{
-			conc:  5,
-			paths: []string{"path1/path2/file.yaml"},
-		},
-		mock: struct {
-			readFileContentResponse struct {
-				data   string
-				errors error
-			}
-			getFilesPathsResponse struct {
-				data   chan string
-				errors chan error
-			}
-		}{
-			readFileContentResponse: struct {
-				data   string
-				errors error
-			}{
-				data:   "apiVersion: extensions/v1beta1",
-				errors: nil,
-			},
-			getFilesPathsResponse: struct {
-				data   chan string
-				errors chan error
-			}{
-				data:   mock_createFilesPathsChannel("path1/path2/file.yaml"),
-				errors: mock_createErrorsChannel(nil),
-			},
-		},
-		expected: struct {
-			readFileContentCalledWith []string
-			filesProperties           []*FileProperties
-			fileErrors                []FileError
-			errors                    []error
-		}{
-			filesProperties:           []*FileProperties{{FileName: "path1/path2/file.yaml", Configurations: []K8sConfiguration{{"apiVersion": "extensions/v1beta1"}}}},
-			fileErrors:                []FileError{},
-			errors:                    []error{},
-			readFileContentCalledWith: []string{"path1/path2/file.yaml"},
-		},
-	}
-}
-
-func test_readFileFromPattern_invalidYaml() readFilesFromPatternTestCase {
-	return readFilesFromPatternTestCase{
-		name: "invalid yaml",
-		args: struct {
-			conc  int
-			paths []string
-		}{
-			conc:  5,
-			paths: []string{"path1/path2/file.yaml"},
-		},
-		mock: struct {
-			readFileContentResponse struct {
-				data   string
-				errors error
-			}
-			getFilesPathsResponse struct {
-				data   chan string
-				errors chan error
-			}
-		}{
-			readFileContentResponse: struct {
-				data   string
-				errors error
-			}{
-				data:   "invalid data",
-				errors: nil,
-			},
-			getFilesPathsResponse: struct {
-				data   chan string
-				errors chan error
-			}{
-				data:   mock_createFilesPathsChannel("path1/path2/file.yaml"),
-				errors: mock_createErrorsChannel(fmt.Errorf("unmarshal yaml")),
-			},
-		},
-		expected: struct {
-			readFileContentCalledWith []string
-			filesProperties           []*FileProperties
-			fileErrors                []FileError
-			errors                    []error
-		}{
-			filesProperties:           []*FileProperties{},
-			fileErrors:                []FileError{},
-			errors:                    []error{fmt.Errorf("unmarshal yaml")},
-			readFileContentCalledWith: []string{"path1/path2/file.yaml"},
-		},
-	}
-}
-
-func test_readFileFromPattern_invalidFile() readFilesFromPatternTestCase {
-	return readFilesFromPatternTestCase{
-		name: "invalid yaml",
-		args: struct {
-			conc  int
-			paths []string
-		}{
-			conc:  5,
-			paths: []string{"path1/path2/file.yaml"},
-		},
-		mock: struct {
-			readFileContentResponse struct {
-				data   string
-				errors error
-			}
-			getFilesPathsResponse struct {
-				data   chan string
-				errors chan error
-			}
-		}{
-			readFileContentResponse: struct {
-				data   string
-				errors error
-			}{
-				data:   "invalid data",
-				errors: fmt.Errorf("invalid file"),
-			},
-			getFilesPathsResponse: struct {
-				data   chan string
-				errors chan error
-			}{
-				data:   mock_createFilesPathsChannel("path1/path2/file.yaml"),
-				errors: mock_createErrorsChannel(fmt.Errorf("unmarshal yaml")),
-			},
-		},
-		expected: struct {
-			readFileContentCalledWith []string
-			filesProperties           []*FileProperties
-			fileErrors                []FileError
-			errors                    []error
-		}{
-			filesProperties:           []*FileProperties{},
-			fileErrors:                []FileError{{Message: "invalid file", Filename: "path1/path2/file.yaml"}},
-			errors:                    []error{fmt.Errorf("unmarshal yaml")},
-			readFileContentCalledWith: []string{"path1/path2/file.yaml"},
-		},
-	}
-}
-
-func mock_createFilesPathsChannel(filepath string) chan string {
-	pathsChan := make(chan string, 1)
-	pathsChan <- filepath
-	close(pathsChan)
-
-	return pathsChan
-}
-
-func mock_createErrorsChannel(err error) chan error {
-	errorsChan := make(chan error, 1)
-
-	if err != nil {
-		errorsChan <- err
-	}
-
-	close(errorsChan)
-
-	return errorsChan
-}