Repository: datreeio/datree
Pull request #531 (2022-03-28 09:23:01)

feat: skip k8s validation when no connection


Patch:
diff --git a/bl/evaluation/non_interactive_evaluator_types.go b/bl/evaluation/non_interactive_evaluator_types.go
index 5efb475..df54078 100644
--- a/bl/evaluation/non_interactive_evaluator_types.go
+++ b/bl/evaluation/non_interactive_evaluator_types.go
@@ -30,11 +30,11 @@ type RuleResult struct {
 }
 
 type NonInteractiveEvaluationSummary struct {
-	ConfigsCount                int `yaml:"configsCount" json:"configsCount" xml:"configsCount"`
-	FilesCount                  int `yaml:"filesCount" json:"filesCount" xml:"filesCount"`
-	PassedYamlValidationCount   int `yaml:"passedYamlValidationCount" json:"passedYamlValidationCount" xml:"passedYamlValidationCount"`
-	PassedK8sValidationCount    int `yaml:"passedK8sValidationCount" json:"passedK8sValidationCount" xml:"passedK8sValidationCount"`
-	PassedPolicyValidationCount int `yaml:"passedPolicyValidationCount" json:"passedPolicyValidationCount" xml:"passedPolicyValidationCount"`
+	ConfigsCount                int    `yaml:"configsCount" json:"configsCount" xml:"configsCount"`
+	FilesCount                  int    `yaml:"filesCount" json:"filesCount" xml:"filesCount"`
+	PassedYamlValidationCount   int    `yaml:"passedYamlValidationCount" json:"passedYamlValidationCount" xml:"passedYamlValidationCount"`
+	K8sValidation               string `yaml:"k8sValidation" json:"k8sValidation" xml:"k8sValidation"`
+	PassedPolicyValidationCount int    `yaml:"passedPolicyValidationCount" json:"passedPolicyValidationCount" xml:"passedPolicyValidationCount"`
 }
 
 type PolicySummary struct {
diff --git a/bl/evaluation/printer.go b/bl/evaluation/printer.go
index 9cc076f..ae44653 100644
--- a/bl/evaluation/printer.go
+++ b/bl/evaluation/printer.go
@@ -9,6 +9,7 @@ import (
 	"sort"
 	"strings"
 
+	"github.com/datreeio/datree/bl/validation"
 	"github.com/datreeio/datree/pkg/extractor"
 
 	"github.com/datreeio/datree/pkg/printer"
@@ -21,9 +22,34 @@ type Printer interface {
 	PrintEvaluationSummary(summary printer.EvaluationSummary, k8sVersion string)
 }
 
-func PrintResults(results FormattedResults, invalidYamlFiles []*extractor.InvalidFile, invalidK8sFiles []*extractor.InvalidFile, evaluationSummary printer.EvaluationSummary, loginURL string, outputFormat string, printer Printer, k8sVersion string, policyName string) error {
-	if outputFormat == "json" || outputFormat == "yaml" || outputFormat == "xml" {
-		nonInteractiveEvaluationResults := results.NonInteractiveEvaluationResults
+type PrintResultsData struct {
+	Results               FormattedResults
+	InvalidYamlFiles      []*extractor.InvalidFile
+	InvalidK8sFiles       []*extractor.InvalidFile
+	EvaluationSummary     printer.EvaluationSummary
+	LoginURL              string
+	OutputFormat          string
+	Printer               Printer
+	K8sVersion            string
+	PolicyName            string
+	K8sValidationWarnings validation.K8sValidationWarningPerValidFile
+}
+
+type textOutputData struct {
+	results               *EvaluationResults
+	invalidYamlFiles      []*extractor.InvalidFile
+	invalidK8sFiles       []*extractor.InvalidFile
+	evaluationSummary     printer.EvaluationSummary
+	url                   string
+	printer               Printer
+	k8sVersion            string
+	policyName            string
+	k8sValidationWarnings validation.K8sValidationWarningPerValidFile
+}
+
+func PrintResults(resultsData *PrintResultsData) error {
+	if resultsData.OutputFormat == "json" || resultsData.OutputFormat == "yaml" || resultsData.OutputFormat == "xml" {
+		nonInteractiveEvaluationResults := resultsData.Results.NonInteractiveEvaluationResults
 		if nonInteractiveEvaluationResults == nil {
 			nonInteractiveEvaluationResults = &NonInteractiveEvaluationResults{}
 		}
@@ -31,25 +57,35 @@ func PrintResults(results FormattedResults, invalidYamlFiles []*extractor.Invali
 			PolicyValidationResults: nonInteractiveEvaluationResults.FormattedEvaluationResults,
 			PolicySummary:           nonInteractiveEvaluationResults.PolicySummary,
 			EvaluationSummary: NonInteractiveEvaluationSummary{
-				ConfigsCount:                evaluationSummary.ConfigsCount,
-				FilesCount:                  evaluationSummary.FilesCount,
-				PassedYamlValidationCount:   evaluationSummary.PassedYamlValidationCount,
-				PassedK8sValidationCount:    evaluationSummary.PassedK8sValidationCount,
-				PassedPolicyValidationCount: evaluationSummary.PassedPolicyCheckCount,
+				ConfigsCount:                resultsData.EvaluationSummary.ConfigsCount,
+				FilesCount:                  resultsData.EvaluationSummary.FilesCount,
+				PassedYamlValidationCount:   resultsData.EvaluationSummary.PassedYamlValidationCount,
+				K8sValidation:               resultsData.EvaluationSummary.K8sValidation,
+				PassedPolicyValidationCount: resultsData.EvaluationSummary.PassedPolicyCheckCount,
 			},
-			YamlValidationResults: invalidYamlFiles,
-			K8sValidationResults:  invalidK8sFiles,
+			YamlValidationResults: resultsData.InvalidYamlFiles,
+			K8sValidationResults:  resultsData.InvalidK8sFiles,
 		}
 
-		if outputFormat == "json" {
+		if resultsData.OutputFormat == "json" {
 			return jsonOutput(&formattedOutput)
-		} else if outputFormat == "yaml" {
+		} else if resultsData.OutputFormat == "yaml" {
 			return yamlOutput(&formattedOutput)
 		} else {
 			return xmlOutput(&formattedOutput)
 		}
 	} else {
-		return textOutput(results.EvaluationResults, invalidYamlFiles, invalidK8sFiles, evaluationSummary, loginURL, printer, k8sVersion, policyName)
+		return textOutput(textOutputData{
+			results:               resultsData.Results.EvaluationResults,
+			invalidYamlFiles:      resultsData.InvalidYamlFiles,
+			invalidK8sFiles:       resultsData.InvalidK8sFiles,
+			evaluationSummary:     resultsData.EvaluationSummary,
+			url:                   resultsData.LoginURL,
+			printer:               resultsData.Printer,
+			k8sVersion:            resultsData.K8sVersion,
+			policyName:            resultsData.PolicyName,
+			k8sValidationWarnings: resultsData.K8sValidationWarnings,
+		})
 	}
 }
 
@@ -87,25 +123,25 @@ func xmlOutput(formattedOutput *FormattedOutput) error {
 	return nil
 }
 
-func textOutput(results *EvaluationResults, invalidYamlFiles []*extractor.InvalidFile, invalidK8sFiles []*extractor.InvalidFile, evaluationSummary printer.EvaluationSummary, url string, printer Printer, k8sVersion string, policyName string) error {
+func textOutput(outputData textOutputData) error {
 	pwd, err := os.Getwd()
 	if err != nil {
 		return err
 	}
 
-	warnings, err := parseToPrinterWarnings(results, invalidYamlFiles, invalidK8sFiles, pwd, k8sVersion)
+	warnings, err := parseToPrinterWarnings(outputData.results, outputData.invalidYamlFiles, outputData.invalidK8sFiles, pwd, outputData.k8sVersion, outputData.k8sValidationWarnings)
 	if err != nil {
 		fmt.Println(err)
 		return err
 	}
 
-	printer.PrintWarnings(warnings)
+	outputData.printer.PrintWarnings(warnings)
 
-	summary := parseEvaluationResultsToSummary(results, evaluationSummary, url, policyName)
+	summary := parseEvaluationResultsToSummary(outputData.results, outputData.evaluationSummary, outputData.url, outputData.policyName)
 
-	printer.PrintEvaluationSummary(evaluationSummary, k8sVersion)
+	outputData.printer.PrintEvaluationSummary(outputData.evaluationSummary, outputData.k8sVersion)
 
-	printer.PrintSummaryTable(summary)
+	outputData.printer.PrintSummaryTable(summary)
 
 	return nil
 }
@@ -144,7 +180,7 @@ func parseInvalidK8sFilesToWarnings(invalidK8sFiles []*extractor.InvalidFile, k8
 	return warnings
 }
 
-func parseToPrinterWarnings(results *EvaluationResults, invalidYamlFiles []*extractor.InvalidFile, invalidK8sFiles []*extractor.InvalidFile, pwd string, k8sVersion string) ([]printer.Warning, error) {
+func parseToPrinterWarnings(results *EvaluationResults, invalidYamlFiles []*extractor.InvalidFile, invalidK8sFiles []*extractor.InvalidFile, pwd string, k8sVersion string, k8sValidationWarnings validation.K8sValidationWarningPerValidFile) ([]printer.Warning, error) {
 	var warnings = []printer.Warning{}
 
 	warnings = append(warnings, parseInvalidYamlFilesToWarnings(invalidYamlFiles)...)
@@ -192,7 +228,9 @@ func parseToPrinterWarnings(results *EvaluationResults, invalidYamlFiles []*extr
 				Title:           fmt.Sprintf(">>  File: %s\n", relativePath),
 				FailedRules:     failedRules,
 				InvalidYamlInfo: printer.InvalidYamlInfo{},
-				InvalidK8sInfo:  printer.InvalidK8sInfo{},
+				InvalidK8sInfo: printer.InvalidK8sInfo{
+					ValidationWarning: k8sValidationWarnings[filename],
+				},
 			})
 		}
 	}
@@ -214,6 +252,7 @@ func GetWarningExtraMessages(invalidFile *extractor.InvalidFile) []printer.Extra
 			Color: "cyan",
 		})
 	}
+
 	return extraMessages
 }
 
diff --git a/bl/validation/k8sValidator.go b/bl/validation/k8sValidator.go
index 77fe1af..8b8bc74 100644
--- a/bl/validation/k8sValidator.go
+++ b/bl/validation/k8sValidator.go
@@ -18,6 +18,8 @@ type K8sValidator struct {
 	validationClient ValidationClient
 }
 
+type K8sValidationWarningPerValidFile map[string]string
+
 func New() *K8sValidator {
 	return &K8sValidator{}
 }
@@ -26,28 +28,41 @@ func (val *K8sValidator) InitClient(k8sVersion string, ignoreMissingSchemas bool
 	val.validationClient = newKubconformValidator(k8sVersion, ignoreMissingSchemas, append(getDefaultSchemaLocation(), schemaLocations...))
 }
 
-func (val *K8sValidator) ValidateResources(filesConfigurationsChan chan *extractor.FileConfigurations, concurrency int) (chan *extractor.FileConfigurations, chan *extractor.InvalidFile) {
+type FileWithWarning struct {
+	Filename string
+	Warning  string
+}
+
+func (val *K8sValidator) ValidateResources(filesConfigurationsChan chan *extractor.FileConfigurations, concurrency int) (chan *extractor.FileConfigurations, chan *extractor.InvalidFile, chan *FileWithWarning) {
 	validK8sFilesConfigurationsChan := make(chan *extractor.FileConfigurations, concurrency)
 	invalidK8sFilesChan := make(chan *extractor.InvalidFile, concurrency)
+	k8sValidationWarningPerValidFileChan := make(chan *FileWithWarning, concurrency)
 
 	go func() {
+
 		defer func() {
 			close(invalidK8sFilesChan)
 			close(validK8sFilesConfigurationsChan)
+			close(k8sValidationWarningPerValidFileChan)
 		}()
 
 		for fileConfigurations := range filesConfigurationsChan {
 
-			isValid, validationErrors, err := val.validateResource(fileConfigurations.FileName)
+			isValid, validationErrors, validationWarning, err := val.validateResource(fileConfigurations.FileName)
 			if err != nil {
 				invalidK8sFilesChan <- &extractor.InvalidFile{
 					Path:             fileConfigurations.FileName,
 					ValidationErrors: []error{err},
 				}
-				continue
 			}
 			if isValid {
 				validK8sFilesConfigurationsChan <- fileConfigurations
+				if validationWarning != "" {
+					k8sValidationWarningPerValidFileChan <- &FileWithWarning{
+						Filename: fileConfigurations.FileName,
+						Warning:  validationWarning,
+					}
+				}
 			} else {
 				invalidK8sFilesChan <- &extractor.InvalidFile{
 					Path:             fileConfigurations.FileName,
@@ -56,7 +71,8 @@ func (val *K8sValidator) ValidateResources(filesConfigurationsChan chan *extract
 			}
 		}
 	}()
-	return validK8sFilesConfigurationsChan, invalidK8sFilesChan
+
+	return validK8sFilesConfigurationsChan, invalidK8sFilesChan, k8sValidationWarningPerValidFileChan
 }
 
 func (val *K8sValidator) GetK8sFiles(filesConfigurationsChan chan *extractor.FileConfigurations, concurrency int) (chan *extractor.FileConfigurations, chan *extractor.FileConfigurations) {
@@ -94,11 +110,12 @@ func (val *K8sValidator) isK8sFile(fileConfigurations []extractor.Configuration)
 	return true
 }
 
-func (val *K8sValidator) validateResource(filepath string) (bool, []error, error) {
+func (val *K8sValidator) validateResource(filepath string) (bool, []error, string, error) {
 	f, err := os.Open(filepath)
 	if err != nil {
-		return false, []error{}, fmt.Errorf("failed opening %s: %s", filepath, &InvalidK8sSchemaError{ErrorMessage: err.Error()})
+		return false, []error{}, "", fmt.Errorf("failed opening %s: %s", filepath, &InvalidK8sSchemaError{ErrorMessage: err.Error()})
 	}
+
 	defer f.Close()
 
 	results := val.validationClient.Validate(filepath, f)
@@ -106,17 +123,21 @@ func (val *K8sValidator) validateResource(filepath string) (bool, []error, error
 	// Return an error if no valid configurations found
 	// Empty files are throwing errors in k8s
 	if isEveryResultStatusEmpty(results) {
-		return false, []error{&InvalidK8sSchemaError{ErrorMessage: "empty file"}}, nil
+		return false, []error{&InvalidK8sSchemaError{ErrorMessage: "empty file"}}, "", nil
 	}
 
 	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 {
+			if val.isNetworkError(res.Err.Error()) {
+				noConnectionWarning := "k8s schema validation skipped: no internet connection"
+				return isValid, []error{}, noConnectionWarning, nil
+			}
 			isValid = false
+
 			errorMessages := strings.Split(res.Err.Error(), "-")
 
 			// errorMessages slice is not empty
@@ -129,7 +150,11 @@ func (val *K8sValidator) validateResource(filepath string) (bool, []error, error
 		}
 	}
 
-	return isValid, validationErrors, nil
+	return isValid, validationErrors, "", nil
+}
+
+func (val *K8sValidator) isNetworkError(errorString string) bool {
+	return strings.Contains(errorString, "no such host")
 }
 
 func newKubconformValidator(k8sVersion string, ignoreMissingSchemas bool, schemaLocations []string) ValidationClient {
diff --git a/pkg/cliClient/evaluation.go b/pkg/cliClient/evaluation.go
index bdd9a77..d379e72 100644
--- a/pkg/cliClient/evaluation.go
+++ b/pkg/cliClient/evaluation.go
@@ -102,7 +102,7 @@ type EvaluationPrerunDataResponse struct {
 
 func (c *CliClient) RequestEvaluationPrerunData(tokenId string) (*EvaluationPrerunDataResponse, error) {
 	if c.networkValidator.IsLocalMode() {
-		return &EvaluationPrerunDataResponse{}, nil
+		return &EvaluationPrerunDataResponse{IsPolicyAsCodeMode: true}, nil
 	}
 
 	res, err := c.httpClient.Request(http.MethodGet, "/cli/evaluation/tokens/"+tokenId+"/prerun", nil, nil)
@@ -120,7 +120,7 @@ func (c *CliClient) RequestEvaluationPrerunData(tokenId string) (*EvaluationPrer
 		return &EvaluationPrerunDataResponse{}, err
 	}
 
-	var evaluationPrerunDataResponse = &EvaluationPrerunDataResponse{}
+	var evaluationPrerunDataResponse = &EvaluationPrerunDataResponse{IsPolicyAsCodeMode: true}
 	err = json.Unmarshal(res.Body, &evaluationPrerunDataResponse)
 	if err != nil {
 		return &EvaluationPrerunDataResponse{}, err
diff --git a/pkg/printer/printer.go b/pkg/printer/printer.go
index a2ba387..5328113 100644
--- a/pkg/printer/printer.go
+++ b/pkg/printer/printer.go
@@ -39,8 +39,9 @@ type InvalidYamlInfo struct {
 	ValidationErrors []error
 }
 type InvalidK8sInfo struct {
-	ValidationErrors []error
-	K8sVersion       string
+	ValidationErrors  []error
+	ValidationWarning string
+	K8sVersion        string
 }
 
 type ExtraMessage struct {
@@ -75,7 +76,7 @@ func (p *Printer) printYamlValidationWarning(warning Warning) {
 	fmt.Fprintln(out)
 }
 
-func (p *Printer) printK8sValidationWarning(warning Warning) {
+func (p *Printer) printK8sValidationError(warning Warning) {
 	p.printPassedYamlValidation()
 	p.printInColor("[X] Kubernetes schema validation\n", p.Theme.Colors.White)
 	fmt.Fprintln(out)
@@ -95,6 +96,14 @@ func (p *Printer) printK8sValidationWarning(warning Warning) {
 	fmt.Fprintln(out)
 }
 
+func (p *Printer) printK8sValidationWarning(warning Warning) {
+	fmt.Println("[?] Kubernetes schema validation")
+	fmt.Fprintln(out)
+
+	fmt.Println(warning.InvalidK8sInfo.ValidationWarning)
+	fmt.Fprintln(out)
+}
+
 func (p *Printer) PrintYamlSchemaResults(result *JSONSchemaValidatorResults, error error) {
 	if result == nil {
 		p.printInColor("INVALID FILE PATH\n", p.Theme.Colors.RedBold)
@@ -125,10 +134,15 @@ func (p *Printer) PrintWarnings(warnings []Warning) {
 		if len(warning.InvalidYamlInfo.ValidationErrors) > 0 {
 			p.printYamlValidationWarning(warning)
 		} else if len(warning.InvalidK8sInfo.ValidationErrors) > 0 {
-			p.printK8sValidationWarning(warning)
+			p.printK8sValidationError(warning)
 		} else {
 			p.printPassedYamlValidation()
-			p.printInColor("[V] Kubernetes schema validation\n", p.Theme.Colors.Green)
+
+			if warning.InvalidK8sInfo.ValidationWarning != "" {
+				p.printK8sValidationWarning(warning)
+			} else {
+				p.printInColor("[V] Kubernetes schema validation\n", p.Theme.Colors.Green)
+			}
 
 			fmt.Fprintln(out)
 			p.printInColor("[X] Policy check\n", p.Theme.Colors.White)
@@ -177,7 +191,7 @@ type EvaluationSummary struct {
 	RulesCount                int
 	FilesCount                int
 	PassedYamlValidationCount int
-	PassedK8sValidationCount  int
+	K8sValidation             string
 	PassedPolicyCheckCount    int
 }
 
@@ -187,7 +201,7 @@ func (p *Printer) PrintEvaluationSummary(summary EvaluationSummary, k8sVersion s
 
 	fmt.Fprintf(out, "- Passing YAML validation: %v/%v\n", summary.PassedYamlValidationCount, summary.FilesCount)
 	fmt.Fprintln(out)
-	fmt.Fprintf(out, "- Passing Kubernetes (%s) schema validation: %v/%v\n", k8sVersion, summary.PassedK8sValidationCount, summary.FilesCount)
+	fmt.Fprintf(out, "- Passing Kubernetes (%s) schema validation: %s\n", k8sVersion, summary.K8sValidation)
 	fmt.Fprintln(out)
 	fmt.Fprintf(out, "- Passing policy check: %v/%v\n", summary.PassedPolicyCheckCount, summary.FilesCount)
 	fmt.Fprintln(out)


Test patch:
diff --git a/bl/evaluation/printer_test.go b/bl/evaluation/printer_test.go
index 50ca83c..d535f8e 100644
--- a/bl/evaluation/printer_test.go
+++ b/bl/evaluation/printer_test.go
@@ -7,6 +7,7 @@ import (
 	"os"
 	"testing"
 
+	"github.com/datreeio/datree/bl/validation"
 	"github.com/datreeio/datree/pkg/extractor"
 
 	"github.com/datreeio/datree/pkg/printer"
@@ -66,7 +67,7 @@ func TestPrintResults(t *testing.T) {
 		mockedPrinter.On("PrintEvaluationSummary", mock.Anything, mock.Anything)
 
 		t.Run(tt.name, func(t *testing.T) {
-			_ = PrintResults(tt.args.results, tt.args.invalidYamlFiles, tt.args.invalidK8sFiles, tt.args.evaluationSummary, tt.args.loginURL, tt.args.outputFormat, mockedPrinter, "1.18.0", "Default")
+			_ = PrintResults(&PrintResultsData{tt.args.results, tt.args.invalidYamlFiles, tt.args.invalidK8sFiles, tt.args.evaluationSummary, tt.args.loginURL, tt.args.outputFormat, mockedPrinter, "1.18.0", "Default", validation.K8sValidationWarningPerValidFile{}})
 
 			if tt.args.outputFormat == "json" {
 				mockedPrinter.AssertNotCalled(t, "PrintWarnings")
@@ -76,7 +77,7 @@ func TestPrintResults(t *testing.T) {
 				mockedPrinter.AssertNotCalled(t, "PrintWarnings")
 			} else {
 				pwd, _ := os.Getwd()
-				warnings, _ := parseToPrinterWarnings(tt.args.results.EvaluationResults, tt.args.invalidYamlFiles, tt.args.invalidK8sFiles, pwd, "1.18.0")
+				warnings, _ := parseToPrinterWarnings(tt.args.results.EvaluationResults, tt.args.invalidYamlFiles, tt.args.invalidK8sFiles, pwd, "1.18.0", validation.K8sValidationWarningPerValidFile{})
 				mockedPrinter.AssertCalled(t, "PrintWarnings", warnings)
 			}
 		})
@@ -190,7 +191,7 @@ func createFormattedOutput() FormattedOutput {
 			ConfigsCount:                1,
 			FilesCount:                  1,
 			PassedYamlValidationCount:   1,
-			PassedK8sValidationCount:    1,
+			K8sValidation:               "1/1",
 			PassedPolicyValidationCount: 0,
 		},
 	}
@@ -198,9 +199,9 @@ func createFormattedOutput() FormattedOutput {
 
 func getExpectedOutputs() expectedOutputs {
 	return expectedOutputs{
-		json: "{\"policyValidationResults\":[{\"fileName\":\"File1\",\"ruleResults\":[{\"identifier\":\"CONTAINERS_MISSING_IMAGE_VALUE_VERSION\",\"name\":\"Ensure each container image has a pinned (tag) version\",\"messageOnFailure\":\"Incorrect value for key `image` - specify an image version to avoid unpleasant \\\"version surprises\\\" in the future\",\"occurrencesDetails\":[{\"metadataName\":\"rss-site\",\"kind\":\"Deployment\",\"occurrences\":1}]},{\"identifier\":\"CONTAINERS_MISSING_MEMORY_LIMIT_KEY\",\"name\":\"Ensure each container has a configured memory limit\",\"messageOnFailure\":\"Missing property object `limits.memory` - value should be within the accepted boundaries recommended by the organization\",\"occurrencesDetails\":[{\"metadataName\":\"rss-site\",\"kind\":\"Deployment\",\"occurrences\":1}]},{\"identifier\":\"WORKLOAD_INVALID_LABELS_VALUE\",\"name\":\"Ensure workload has valid label values\",\"messageOnFailure\":\"Incorrect value for key(s) under `labels` - the vales syntax is not valid so the Kubernetes engine will not accept it\",\"occurrencesDetails\":[{\"metadataName\":\"rss-site\",\"kind\":\"Deployment\",\"occurrences\":1}]},{\"identifier\":\"CONTAINERS_MISSING_LIVENESSPROBE_KEY\",\"name\":\"Ensure each container has a configured liveness probe\",\"messageOnFailure\":\"Missing property object `livenessProbe` - add a properly configured livenessProbe to catch possible deadlocks\",\"occurrencesDetails\":[{\"metadataName\":\"rss-site\",\"kind\":\"Deployment\",\"occurrences\":1}]}]}],\"policySummary\":{\"policyName\":\"Default\",\"totalRulesInPolicy\":21,\"totalRulesFailed\":4,\"totalPassedCount\":0},\"evaluationSummary\":{\"configsCount\":1,\"filesCount\":1,\"passedYamlValidationCount\":1,\"passedK8sValidationCount\":1,\"passedPolicyValidationCount\":0},\"yamlValidationResults\":null,\"k8sValidationResults\":null}\n",
-		yaml: "policyValidationResults:\n- fileName: File1\n  ruleResults:\n  - identifier: CONTAINERS_MISSING_IMAGE_VALUE_VERSION\n    name: Ensure each container image has a pinned (tag) version\n    messageOnFailure: Incorrect value for key `image` - specify an image version to\n      avoid unpleasant \"version surprises\" in the future\n    occurrencesDetails:\n    - metadataName: rss-site\n      kind: Deployment\n      occurrences: 1\n  - identifier: CONTAINERS_MISSING_MEMORY_LIMIT_KEY\n    name: Ensure each container has a configured memory limit\n    messageOnFailure: Missing property object `limits.memory` - value should be within\n      the accepted boundaries recommended by the organization\n    occurrencesDetails:\n    - metadataName: rss-site\n      kind: Deployment\n      occurrences: 1\n  - identifier: WORKLOAD_INVALID_LABELS_VALUE\n    name: Ensure workload has valid label values\n    messageOnFailure: Incorrect value for key(s) under `labels` - the vales syntax\n      is not valid so the Kubernetes engine will not accept it\n    occurrencesDetails:\n    - metadataName: rss-site\n      kind: Deployment\n      occurrences: 1\n  - identifier: CONTAINERS_MISSING_LIVENESSPROBE_KEY\n    name: Ensure each container has a configured liveness probe\n    messageOnFailure: Missing property object `livenessProbe` - add a properly configured\n      livenessProbe to catch possible deadlocks\n    occurrencesDetails:\n    - metadataName: rss-site\n      kind: Deployment\n      occurrences: 1\npolicySummary:\n  policyName: Default\n  totalRulesInPolicy: 21\n  totalRulesFailed: 4\n  totalPassedCount: 0\nevaluationSummary:\n  configsCount: 1\n  filesCount: 1\n  passedYamlValidationCount: 1\n  passedK8sValidationCount: 1\n  passedPolicyValidationCount: 0\nyamlValidationResults: []\nk8sValidationResults: []\n\n",
-		xml:  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<FormattedOutput>\n\t<policyValidationResults>\n\t\t<fileName>File1</fileName>\n\t\t<ruleResults>\n\t\t\t<identifier>CONTAINERS_MISSING_IMAGE_VALUE_VERSION</identifier>\n\t\t\t<name>Ensure each container image has a pinned (tag) version</name>\n\t\t\t<messageOnFailure>Incorrect value for key `image` - specify an image version to avoid unpleasant &#34;version surprises&#34; in the future</messageOnFailure>\n\t\t\t<occurrencesDetails>\n\t\t\t\t<metadataName>rss-site</metadataName>\n\t\t\t\t<kind>Deployment</kind>\n\t\t\t\t<occurrences>1</occurrences>\n\t\t\t</occurrencesDetails>\n\t\t</ruleResults>\n\t\t<ruleResults>\n\t\t\t<identifier>CONTAINERS_MISSING_MEMORY_LIMIT_KEY</identifier>\n\t\t\t<name>Ensure each container has a configured memory limit</name>\n\t\t\t<messageOnFailure>Missing property object `limits.memory` - value should be within the accepted boundaries recommended by the organization</messageOnFailure>\n\t\t\t<occurrencesDetails>\n\t\t\t\t<metadataName>rss-site</metadataName>\n\t\t\t\t<kind>Deployment</kind>\n\t\t\t\t<occurrences>1</occurrences>\n\t\t\t</occurrencesDetails>\n\t\t</ruleResults>\n\t\t<ruleResults>\n\t\t\t<identifier>WORKLOAD_INVALID_LABELS_VALUE</identifier>\n\t\t\t<name>Ensure workload has valid label values</name>\n\t\t\t<messageOnFailure>Incorrect value for key(s) under `labels` - the vales syntax is not valid so the Kubernetes engine will not accept it</messageOnFailure>\n\t\t\t<occurrencesDetails>\n\t\t\t\t<metadataName>rss-site</metadataName>\n\t\t\t\t<kind>Deployment</kind>\n\t\t\t\t<occurrences>1</occurrences>\n\t\t\t</occurrencesDetails>\n\t\t</ruleResults>\n\t\t<ruleResults>\n\t\t\t<identifier>CONTAINERS_MISSING_LIVENESSPROBE_KEY</identifier>\n\t\t\t<name>Ensure each container has a configured liveness probe</name>\n\t\t\t<messageOnFailure>Missing property object `livenessProbe` - add a properly configured livenessProbe to catch possible deadlocks</messageOnFailure>\n\t\t\t<occurrencesDetails>\n\t\t\t\t<metadataName>rss-site</metadataName>\n\t\t\t\t<kind>Deployment</kind>\n\t\t\t\t<occurrences>1</occurrences>\n\t\t\t</occurrencesDetails>\n\t\t</ruleResults>\n\t</policyValidationResults>\n\t<policySummary>\n\t\t<policyName>Default</policyName>\n\t\t<totalRulesInPolicy>21</totalRulesInPolicy>\n\t\t<totalRulesFailed>4</totalRulesFailed>\n\t\t<totalPassedCount>0</totalPassedCount>\n\t</policySummary>\n\t<evaluationSummary>\n\t\t<configsCount>1</configsCount>\n\t\t<filesCount>1</filesCount>\n\t\t<passedYamlValidationCount>1</passedYamlValidationCount>\n\t\t<passedK8sValidationCount>1</passedK8sValidationCount>\n\t\t<passedPolicyValidationCount>0</passedPolicyValidationCount>\n\t</evaluationSummary>\n</FormattedOutput>\n",
+		json: "{\"policyValidationResults\":[{\"fileName\":\"File1\",\"ruleResults\":[{\"identifier\":\"CONTAINERS_MISSING_IMAGE_VALUE_VERSION\",\"name\":\"Ensure each container image has a pinned (tag) version\",\"messageOnFailure\":\"Incorrect value for key `image` - specify an image version to avoid unpleasant \\\"version surprises\\\" in the future\",\"occurrencesDetails\":[{\"metadataName\":\"rss-site\",\"kind\":\"Deployment\",\"occurrences\":1}]},{\"identifier\":\"CONTAINERS_MISSING_MEMORY_LIMIT_KEY\",\"name\":\"Ensure each container has a configured memory limit\",\"messageOnFailure\":\"Missing property object `limits.memory` - value should be within the accepted boundaries recommended by the organization\",\"occurrencesDetails\":[{\"metadataName\":\"rss-site\",\"kind\":\"Deployment\",\"occurrences\":1}]},{\"identifier\":\"WORKLOAD_INVALID_LABELS_VALUE\",\"name\":\"Ensure workload has valid label values\",\"messageOnFailure\":\"Incorrect value for key(s) under `labels` - the vales syntax is not valid so the Kubernetes engine will not accept it\",\"occurrencesDetails\":[{\"metadataName\":\"rss-site\",\"kind\":\"Deployment\",\"occurrences\":1}]},{\"identifier\":\"CONTAINERS_MISSING_LIVENESSPROBE_KEY\",\"name\":\"Ensure each container has a configured liveness probe\",\"messageOnFailure\":\"Missing property object `livenessProbe` - add a properly configured livenessProbe to catch possible deadlocks\",\"occurrencesDetails\":[{\"metadataName\":\"rss-site\",\"kind\":\"Deployment\",\"occurrences\":1}]}]}],\"policySummary\":{\"policyName\":\"Default\",\"totalRulesInPolicy\":21,\"totalRulesFailed\":4,\"totalPassedCount\":0},\"evaluationSummary\":{\"configsCount\":1,\"filesCount\":1,\"passedYamlValidationCount\":1,\"k8sValidation\":\"1/1\",\"passedPolicyValidationCount\":0},\"yamlValidationResults\":null,\"k8sValidationResults\":null}\n",
+		yaml: "policyValidationResults:\n- fileName: File1\n  ruleResults:\n  - identifier: CONTAINERS_MISSING_IMAGE_VALUE_VERSION\n    name: Ensure each container image has a pinned (tag) version\n    messageOnFailure: Incorrect value for key `image` - specify an image version to\n      avoid unpleasant \"version surprises\" in the future\n    occurrencesDetails:\n    - metadataName: rss-site\n      kind: Deployment\n      occurrences: 1\n  - identifier: CONTAINERS_MISSING_MEMORY_LIMIT_KEY\n    name: Ensure each container has a configured memory limit\n    messageOnFailure: Missing property object `limits.memory` - value should be within\n      the accepted boundaries recommended by the organization\n    occurrencesDetails:\n    - metadataName: rss-site\n      kind: Deployment\n      occurrences: 1\n  - identifier: WORKLOAD_INVALID_LABELS_VALUE\n    name: Ensure workload has valid label values\n    messageOnFailure: Incorrect value for key(s) under `labels` - the vales syntax\n      is not valid so the Kubernetes engine will not accept it\n    occurrencesDetails:\n    - metadataName: rss-site\n      kind: Deployment\n      occurrences: 1\n  - identifier: CONTAINERS_MISSING_LIVENESSPROBE_KEY\n    name: Ensure each container has a configured liveness probe\n    messageOnFailure: Missing property object `livenessProbe` - add a properly configured\n      livenessProbe to catch possible deadlocks\n    occurrencesDetails:\n    - metadataName: rss-site\n      kind: Deployment\n      occurrences: 1\npolicySummary:\n  policyName: Default\n  totalRulesInPolicy: 21\n  totalRulesFailed: 4\n  totalPassedCount: 0\nevaluationSummary:\n  configsCount: 1\n  filesCount: 1\n  passedYamlValidationCount: 1\n  k8sValidation: 1/1\n  passedPolicyValidationCount: 0\nyamlValidationResults: []\nk8sValidationResults: []\n\n",
+		xml:  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<FormattedOutput>\n\t<policyValidationResults>\n\t\t<fileName>File1</fileName>\n\t\t<ruleResults>\n\t\t\t<identifier>CONTAINERS_MISSING_IMAGE_VALUE_VERSION</identifier>\n\t\t\t<name>Ensure each container image has a pinned (tag) version</name>\n\t\t\t<messageOnFailure>Incorrect value for key `image` - specify an image version to avoid unpleasant &#34;version surprises&#34; in the future</messageOnFailure>\n\t\t\t<occurrencesDetails>\n\t\t\t\t<metadataName>rss-site</metadataName>\n\t\t\t\t<kind>Deployment</kind>\n\t\t\t\t<occurrences>1</occurrences>\n\t\t\t</occurrencesDetails>\n\t\t</ruleResults>\n\t\t<ruleResults>\n\t\t\t<identifier>CONTAINERS_MISSING_MEMORY_LIMIT_KEY</identifier>\n\t\t\t<name>Ensure each container has a configured memory limit</name>\n\t\t\t<messageOnFailure>Missing property object `limits.memory` - value should be within the accepted boundaries recommended by the organization</messageOnFailure>\n\t\t\t<occurrencesDetails>\n\t\t\t\t<metadataName>rss-site</metadataName>\n\t\t\t\t<kind>Deployment</kind>\n\t\t\t\t<occurrences>1</occurrences>\n\t\t\t</occurrencesDetails>\n\t\t</ruleResults>\n\t\t<ruleResults>\n\t\t\t<identifier>WORKLOAD_INVALID_LABELS_VALUE</identifier>\n\t\t\t<name>Ensure workload has valid label values</name>\n\t\t\t<messageOnFailure>Incorrect value for key(s) under `labels` - the vales syntax is not valid so the Kubernetes engine will not accept it</messageOnFailure>\n\t\t\t<occurrencesDetails>\n\t\t\t\t<metadataName>rss-site</metadataName>\n\t\t\t\t<kind>Deployment</kind>\n\t\t\t\t<occurrences>1</occurrences>\n\t\t\t</occurrencesDetails>\n\t\t</ruleResults>\n\t\t<ruleResults>\n\t\t\t<identifier>CONTAINERS_MISSING_LIVENESSPROBE_KEY</identifier>\n\t\t\t<name>Ensure each container has a configured liveness probe</name>\n\t\t\t<messageOnFailure>Missing property object `livenessProbe` - add a properly configured livenessProbe to catch possible deadlocks</messageOnFailure>\n\t\t\t<occurrencesDetails>\n\t\t\t\t<metadataName>rss-site</metadataName>\n\t\t\t\t<kind>Deployment</kind>\n\t\t\t\t<occurrences>1</occurrences>\n\t\t\t</occurrencesDetails>\n\t\t</ruleResults>\n\t</policyValidationResults>\n\t<policySummary>\n\t\t<policyName>Default</policyName>\n\t\t<totalRulesInPolicy>21</totalRulesInPolicy>\n\t\t<totalRulesFailed>4</totalRulesFailed>\n\t\t<totalPassedCount>0</totalPassedCount>\n\t</policySummary>\n\t<evaluationSummary>\n\t\t<configsCount>1</configsCount>\n\t\t<filesCount>1</filesCount>\n\t\t<passedYamlValidationCount>1</passedYamlValidationCount>\n\t\t<k8sValidation>1/1</k8sValidation>\n\t\t<passedPolicyValidationCount>0</passedPolicyValidationCount>\n\t</evaluationSummary>\n</FormattedOutput>\n",
 	}
 }
 
diff --git a/bl/validation/k8sValidator_test.go b/bl/validation/k8sValidator_test.go
index 676b001..4d95941 100644
--- a/bl/validation/k8sValidator_test.go
+++ b/bl/validation/k8sValidator_test.go
@@ -3,6 +3,7 @@ package validation
 import (
 	"fmt"
 	"io"
+	"sync"
 	"testing"
 
 	"github.com/datreeio/datree/pkg/extractor"
@@ -27,6 +28,7 @@ func TestValidateResources(t *testing.T) {
 	test_default_schema_location(t)
 	test_get_datree_crd_schema_by_name(t)
 	t.Run("test empty file", test_empty_file)
+	t.Run("test no internet connection", test_no_connection)
 }
 
 func test_valid_multiple_configurations(t *testing.T) {
@@ -46,7 +48,7 @@ func test_valid_multiple_configurations(t *testing.T) {
 		Configurations: []extractor.Configuration{},
 	}
 	close(filesConfigurationsChan)
-	validConfigurationsChan, _ := k8sValidator.ValidateResources(filesConfigurationsChan, 1)
+	validConfigurationsChan, _, _ := k8sValidator.ValidateResources(filesConfigurationsChan, 1)
 
 	for p := range validConfigurationsChan {
 		assert.Equal(t, path, p.FileName)
@@ -94,7 +96,7 @@ func test_invalid_file(t *testing.T) {
 		Configurations: []extractor.Configuration{},
 	}
 	close(filesConfigurationsChan)
-	_, invalidFilesChan := k8sValidator.ValidateResources(filesConfigurationsChan, 1)
+	_, invalidFilesChan, _ := k8sValidator.ValidateResources(filesConfigurationsChan, 1)
 
 	for p := range invalidFilesChan {
 		assert.Equal(t, path, p.Path)
@@ -118,13 +120,53 @@ func test_empty_file(t *testing.T) {
 		Configurations: []extractor.Configuration{},
 	}
 	close(filesConfigurationsChan)
-	_, invalidFilesChan := k8sValidator.ValidateResources(filesConfigurationsChan, 1)
+	_, invalidFilesChan, _ := k8sValidator.ValidateResources(filesConfigurationsChan, 1)
 
 	for p := range invalidFilesChan {
 		assert.Equal(t, path, p.Path)
 	}
 }
 
+func test_no_connection(t *testing.T) {
+	validationClient := &mockValidationClient{}
+	validationClient.On("Validate", mock.Anything, mock.Anything).Return([]kubeconformValidator.Result{
+		{Status: kubeconformValidator.Error, Err: fmt.Errorf("no such host")},
+	})
+	k8sValidator := K8sValidator{
+		validationClient: validationClient,
+	}
+
+	path := "../../internal/fixtures/kube/pass-all.yaml"
+
+	filesConfigurationsChan := make(chan *extractor.FileConfigurations, 1)
+	filesConfigurationsChan <- &extractor.FileConfigurations{
+		FileName:       path,
+		Configurations: []extractor.Configuration{},
+	}
+	close(filesConfigurationsChan)
+	k8sValidationWarningPerValidFile := make(K8sValidationWarningPerValidFile)
+
+	var wg sync.WaitGroup
+	filesConfigurationsChanRes, invalidFilesChan, filesWithWarningsChan := k8sValidator.ValidateResources(filesConfigurationsChan, 1)
+	wg.Add(1)
+	go func() {
+		for p := range filesConfigurationsChanRes {
+			_ = p
+		}
+		for p := range invalidFilesChan {
+			_ = p
+		}
+		for p := range filesWithWarningsChan {
+			k8sValidationWarningPerValidFile[p.Filename] = p.Warning
+		}
+		wg.Done()
+	}()
+	wg.Wait()
+
+	assert.Equal(t, 1, len(k8sValidationWarningPerValidFile))
+	assert.Equal(t, "k8s schema validation skipped: no internet connection", k8sValidationWarningPerValidFile["../../internal/fixtures/kube/pass-all.yaml"])
+}
+
 func test_default_schema_location(t *testing.T) {
 	expectedOutput := []string{"default", "https://raw.githubusercontent.com/datreeio/CRDs-catalog/master/argo/{{ .ResourceKind }}_{{ .ResourceAPIVersion }}.json"}
 	actual := getDefaultSchemaLocation()
diff --git a/cmd/kustomize/main_test.go b/cmd/kustomize/main_test.go
index 9999458..05bd7d9 100644
--- a/cmd/kustomize/main_test.go
+++ b/cmd/kustomize/main_test.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/datreeio/datree/bl/evaluation"
 	"github.com/datreeio/datree/bl/messager"
+	"github.com/datreeio/datree/bl/validation"
 	"github.com/datreeio/datree/cmd/test"
 	"github.com/datreeio/datree/pkg/cliClient"
 	"github.com/datreeio/datree/pkg/executor"
@@ -117,9 +118,9 @@ type k8sValidatorMock struct {
 	mock.Mock
 }
 
-func (kv *k8sValidatorMock) ValidateResources(filesConfigurationsChan chan *extractor.FileConfigurations, concurrency int) (chan *extractor.FileConfigurations, chan *extractor.InvalidFile) {
+func (kv *k8sValidatorMock) ValidateResources(filesConfigurationsChan chan *extractor.FileConfigurations, concurrency int) (chan *extractor.FileConfigurations, chan *extractor.InvalidFile, chan *validation.FileWithWarning) {
 	args := kv.Called(filesConfigurationsChan, concurrency)
-	return args.Get(0).(chan *extractor.FileConfigurations), args.Get(1).(chan *extractor.InvalidFile)
+	return args.Get(0).(chan *extractor.FileConfigurations), args.Get(1).(chan *extractor.InvalidFile), args.Get(2).(chan *validation.FileWithWarning)
 }
 
 func (kv *k8sValidatorMock) GetK8sFiles(filesConfigurationsChan chan *extractor.FileConfigurations, concurrency int) (chan *extractor.FileConfigurations, chan *extractor.FileConfigurations) {
diff --git a/cmd/test/main.go b/cmd/test/main.go
index 200897e..f3fb259 100644
--- a/cmd/test/main.go
+++ b/cmd/test/main.go
@@ -11,6 +11,7 @@ import (
 	"github.com/datreeio/datree/bl/files"
 	"github.com/datreeio/datree/bl/messager"
 	policy_factory "github.com/datreeio/datree/bl/policy"
+	"github.com/datreeio/datree/bl/validation"
 	"github.com/datreeio/datree/pkg/ciContext"
 	"github.com/datreeio/datree/pkg/cliClient"
 	"github.com/datreeio/datree/pkg/policy"
@@ -36,7 +37,7 @@ type Messager interface {
 }
 
 type K8sValidator interface {
-	ValidateResources(filesConfigurations chan *extractor.FileConfigurations, concurrency int) (chan *extractor.FileConfigurations, chan *extractor.InvalidFile)
+	ValidateResources(filesConfigurations chan *extractor.FileConfigurations, concurrency int) (chan *extractor.FileConfigurations, chan *extractor.InvalidFile, chan *validation.FileWithWarning)
 	InitClient(k8sVersion string, ignoreMissingSchemas bool, schemaLocations []string)
 	GetK8sFiles(filesConfigurationsChan chan *extractor.FileConfigurations, concurrency int) (chan *extractor.FileConfigurations, chan *extractor.FileConfigurations)
 }
@@ -63,8 +64,6 @@ func NewTestCommandFlags() *TestCommandFlags {
 	}
 }
 
-//
-
 func (flags *TestCommandFlags) Validate() error {
 	outputValue := flags.Output
 
@@ -315,8 +314,8 @@ func Test(ctx *TestCommandContext, paths []string, prerunData *TestCommandData)
 	if err != nil {
 		return err
 	}
-	filesPathsLen := len(filesPaths)
-	if filesPathsLen == 0 {
+	filesCount := len(filesPaths)
+	if filesCount == 0 {
 		noFilesErr := fmt.Errorf("No files detected")
 		return noFilesErr
 	}
@@ -338,18 +337,29 @@ func Test(ctx *TestCommandContext, paths []string, prerunData *TestCommandData)
 
 	validationManager := evaluationResultData.ValidationManager
 
-	passedYamlValidationCount := filesPathsLen - validationManager.InvalidYamlFilesCount()
+	passedYamlValidationCount := filesCount - validationManager.InvalidYamlFilesCount()
 
 	evaluationSummary := printer.EvaluationSummary{
-		FilesCount:                filesPathsLen,
+		FilesCount:                filesCount,
 		RulesCount:                evaluationResultData.RulesCount,
 		PassedYamlValidationCount: passedYamlValidationCount,
-		PassedK8sValidationCount:  validationManager.ValidK8sFilesConfigurationsCount(),
+		K8sValidation:             validationManager.GetK8sValidationSummaryStr(filesCount),
 		ConfigsCount:              validationManager.ValidK8sConfigurationsCount(),
 		PassedPolicyCheckCount:    passedPolicyCheckCount,
 	}
 
-	err = evaluation.PrintResults(results, validationManager.InvalidYamlFiles(), validationManager.InvalidK8sFiles(), evaluationSummary, prerunData.RegistrationURL, prerunData.Output, ctx.Printer, prerunData.K8sVersion, prerunData.Policy.Name)
+	err = evaluation.PrintResults(&evaluation.PrintResultsData{
+		Results:               results,
+		InvalidYamlFiles:      validationManager.InvalidYamlFiles(),
+		InvalidK8sFiles:       validationManager.InvalidK8sFiles(),
+		EvaluationSummary:     evaluationSummary,
+		LoginURL:              prerunData.RegistrationURL,
+		OutputFormat:          prerunData.Output,
+		Printer:               ctx.Printer,
+		K8sVersion:            prerunData.K8sVersion,
+		PolicyName:            prerunData.Policy.Name,
+		K8sValidationWarnings: validationManager.k8sValidationWarningPerValidFile,
+	})
 
 	if evaluationResultData.PromptMessage != "" {
 		ctx.Printer.PrintPromptMessage(evaluationResultData.PromptMessage)
@@ -398,7 +408,7 @@ func evaluate(ctx *TestCommandContext, filesPaths []string, prerunData *TestComm
 		}
 	}()
 
-	validationManager := &ValidationManager{}
+	validationManager := NewValidationManager()
 
 	ctx.K8sValidator.InitClient(prerunData.K8sVersion, prerunData.IgnoreMissingSchemas, prerunData.SchemaLocations)
 
@@ -414,11 +424,11 @@ func evaluate(ctx *TestCommandContext, filesPaths []string, prerunData *TestComm
 		validationManager.AggregateIgnoredYamlFiles(ignoredYamlFilesChan)
 	}
 
-	validK8sFilesConfigurationsChan, invalidK8sFilesChan := ctx.K8sValidator.ValidateResources(validYamlConfigurationsChan, concurrency)
+	validK8sFilesConfigurationsChan, invalidK8sFilesChan, filesWithWarningsChan := ctx.K8sValidator.ValidateResources(validYamlConfigurationsChan, concurrency)
 
 	validationManager.AggregateInvalidK8sFiles(invalidK8sFilesChan)
-
 	validationManager.AggregateValidK8sFiles(validK8sFilesConfigurationsChan)
+	validationManager.AggregateK8sValidationWarningsPerValidFile(filesWithWarningsChan)
 
 	policyName := prerunData.Policy.Name
 
diff --git a/cmd/test/main_test.go b/cmd/test/main_test.go
index 9904f29..2616c45 100644
--- a/cmd/test/main_test.go
+++ b/cmd/test/main_test.go
@@ -4,7 +4,9 @@ import (
 	"encoding/json"
 	"testing"
 
+	"github.com/datreeio/datree/bl/messager"
 	policy_factory "github.com/datreeio/datree/bl/policy"
+	"github.com/datreeio/datree/bl/validation"
 	"github.com/datreeio/datree/pkg/fileReader"
 
 	"github.com/datreeio/datree/pkg/cliClient"
@@ -12,7 +14,6 @@ import (
 	"github.com/datreeio/datree/pkg/printer"
 
 	"github.com/datreeio/datree/bl/evaluation"
-	"github.com/datreeio/datree/bl/messager"
 	"github.com/datreeio/datree/pkg/localConfig"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
@@ -63,9 +64,9 @@ type K8sValidatorMock struct {
 	mock.Mock
 }
 
-func (kv *K8sValidatorMock) ValidateResources(filesConfigurationsChan chan *extractor.FileConfigurations, concurrency int) (chan *extractor.FileConfigurations, chan *extractor.InvalidFile) {
+func (kv *K8sValidatorMock) ValidateResources(filesConfigurationsChan chan *extractor.FileConfigurations, concurrency int) (chan *extractor.FileConfigurations, chan *extractor.InvalidFile, chan *validation.FileWithWarning) {
 	args := kv.Called(filesConfigurationsChan, concurrency)
-	return args.Get(0).(chan *extractor.FileConfigurations), args.Get(1).(chan *extractor.InvalidFile)
+	return args.Get(0).(chan *extractor.FileConfigurations), args.Get(1).(chan *extractor.InvalidFile), args.Get(2).(chan *validation.FileWithWarning)
 }
 
 func (kv *K8sValidatorMock) GetK8sFiles(filesConfigurationsChan chan *extractor.FileConfigurations, concurrency int) (chan *extractor.FileConfigurations, chan *extractor.FileConfigurations) {
@@ -122,8 +123,19 @@ func (lc *LocalConfigMock) GetLocalConfiguration() (*localConfig.LocalConfig, er
 	return &localConfig.LocalConfig{Token: "134kh"}, nil
 }
 
-func TestTestCommand(t *testing.T) {
-	evaluationId := 444
+var filesConfigurations []*extractor.FileConfigurations
+var evaluationId int
+var ctx *TestCommandContext
+var testingPolicy policy_factory.Policy
+
+// mock instances
+var k8sValidatorMock *K8sValidatorMock
+var mockedEvaluator *mockEvaluator
+var localConfigMock *LocalConfigMock
+var messagerMock *mockMessager
+
+func setup() {
+	evaluationId = 444
 
 	prerunData := mockGetPreRunData()
 
@@ -150,24 +162,25 @@ func TestTestCommand(t *testing.T) {
 		PromptMessage: "",
 	}
 
-	mockedEvaluator := &mockEvaluator{}
+	mockedEvaluator = &mockEvaluator{}
 	mockedEvaluator.On("Evaluate", mock.Anything).Return(policyCheckResultData, nil)
 	mockedEvaluator.On("SendEvaluationResult", mock.Anything).Return(sendEvaluationResultsResponse, nil)
 	mockedEvaluator.On("RequestEvaluationPrerunData", mock.Anything).Return(prerunData, nil)
 
-	messager := &mockMessager{}
-	messager.On("LoadVersionMessages", mock.Anything)
+	messagerMock = &mockMessager{}
+	messagerMock.On("LoadVersionMessages", mock.Anything)
 
-	k8sValidatorMock := &K8sValidatorMock{}
+	k8sValidatorMock = &K8sValidatorMock{}
 
 	path := "valid/path"
 	filesConfigurationsChan := newFilesConfigurationsChan(path)
-	filesConfigurations := newFilesConfigurations(path)
+	filesConfigurations = newFilesConfigurations(path)
 
 	invalidK8sFilesChan := newInvalidK8sFilesChan()
 	ignoredFilesChan := newIgnoredYamlFilesChan()
+	k8sValidationWarningsChan := newK8sValidationWarningsChan()
 
-	k8sValidatorMock.On("ValidateResources", mock.Anything, mock.Anything).Return(filesConfigurationsChan, invalidK8sFilesChan, newErrorsChan())
+	k8sValidatorMock.On("ValidateResources", mock.Anything, mock.Anything).Return(filesConfigurationsChan, invalidK8sFilesChan, k8sValidationWarningsChan, newErrorsChan())
 	k8sValidatorMock.On("GetK8sFiles", mock.Anything, mock.Anything).Return(filesConfigurationsChan, ignoredFilesChan, newErrorsChan())
 	k8sValidatorMock.On("InitClient", mock.Anything, mock.Anything, mock.Anything).Return()
 
@@ -182,33 +195,115 @@ func TestTestCommand(t *testing.T) {
 	readerMock := &ReaderMock{}
 	readerMock.On("FilterFiles", mock.Anything).Return([]string{"file/path"}, nil)
 
-	localConfigMock := &LocalConfigMock{}
+	localConfigMock = &LocalConfigMock{}
 	localConfigMock.On("GetLocalConfiguration").Return(&localConfig.LocalConfig{Token: "134kh"}, nil)
 
-	ctx := &TestCommandContext{
+	ctx = &TestCommandContext{
 		K8sValidator: k8sValidatorMock,
 		Evaluator:    mockedEvaluator,
 		LocalConfig:  localConfigMock,
-		Messager:     messager,
+		Messager:     messagerMock,
 		Printer:      printerMock,
 		Reader:       readerMock,
 	}
 
-	policy, _ := policy_factory.CreatePolicy(prerunData.PoliciesJson, "")
+	testingPolicy, _ = policy_factory.CreatePolicy(prerunData.PoliciesJson, "")
+}
+
+func TestTestCommandFlagsValidation(t *testing.T) {
+	setup()
+	test_testCommand_output_flags_validation(t, ctx)
+	test_testCommand_version_flags_validation(t, ctx)
+}
 
-	test_testCommand_flags_validation(t, ctx)
-	test_testCommand_no_flags(t, mockedEvaluator, k8sValidatorMock, filesConfigurations, ctx, policy)
-	test_testCommand_json_output(t, mockedEvaluator, k8sValidatorMock, filesConfigurations, ctx, policy)
-	test_testCommand_yaml_output(t, mockedEvaluator, k8sValidatorMock, filesConfigurations, ctx, policy)
-	test_testCommand_xml_output(t, mockedEvaluator, k8sValidatorMock, filesConfigurations, ctx, policy)
+func TestTestCommandNoFlags(t *testing.T) {
+	setup()
+	_ = Test(ctx, []string{"8/*"}, &TestCommandData{K8sVersion: "1.18.0", Output: "", Policy: testingPolicy, Token: "134kh"})
 
-	test_testCommand_only_k8s_files(t, k8sValidatorMock, filesConfigurations, evaluationId, ctx)
+	policyCheckData := evaluation.PolicyCheckData{
+		FilesConfigurations: filesConfigurations,
+		IsInteractiveMode:   true,
+		PolicyName:          testingPolicy.Name,
+		Policy:              testingPolicy,
+	}
+
+	k8sValidatorMock.AssertCalled(t, "ValidateResources", mock.Anything, 100)
+	mockedEvaluator.AssertCalled(t, "Evaluate", policyCheckData)
 }
 
-func test_testCommand_flags_validation(t *testing.T, ctx *TestCommandContext) {
+func TestTestCommandJsonOutput(t *testing.T) {
+	setup()
+	_ = Test(ctx, []string{"valid/path"}, &TestCommandData{Output: "json", Policy: testingPolicy})
 
-	test_testCommand_output_flags_validation(t, ctx)
-	test_testCommand_version_flags_validation(t, ctx)
+	policyCheckData := evaluation.PolicyCheckData{
+		FilesConfigurations: filesConfigurations,
+		IsInteractiveMode:   false,
+		PolicyName:          testingPolicy.Name,
+		Policy:              testingPolicy,
+	}
+
+	k8sValidatorMock.AssertCalled(t, "ValidateResources", mock.Anything, 100)
+	mockedEvaluator.AssertCalled(t, "Evaluate", policyCheckData)
+}
+
+func TestTestCommandYamlOutput(t *testing.T) {
+	setup()
+	_ = Test(ctx, []string{"8/*"}, &TestCommandData{Output: "yaml", Policy: testingPolicy})
+
+	policyCheckData := evaluation.PolicyCheckData{
+		FilesConfigurations: filesConfigurations,
+		IsInteractiveMode:   false,
+		PolicyName:          testingPolicy.Name,
+		Policy:              testingPolicy,
+	}
+
+	k8sValidatorMock.AssertCalled(t, "ValidateResources", mock.Anything, 100)
+	mockedEvaluator.AssertCalled(t, "Evaluate", policyCheckData)
+}
+
+func TestTestCommandXmlOutput(t *testing.T) {
+	setup()
+	_ = Test(ctx, []string{"valid/path"}, &TestCommandData{Output: "xml", Policy: testingPolicy})
+
+	policyCheckData := evaluation.PolicyCheckData{
+		FilesConfigurations: filesConfigurations,
+		IsInteractiveMode:   false,
+		PolicyName:          testingPolicy.Name,
+		Policy:              testingPolicy,
+	}
+
+	k8sValidatorMock.AssertCalled(t, "ValidateResources", mock.Anything, 100)
+	mockedEvaluator.AssertCalled(t, "Evaluate", policyCheckData)
+}
+
+func TestTestCommandOnlyK8sFiles(t *testing.T) {
+	setup()
+	_ = Test(ctx, []string{"8/*"}, &TestCommandData{OnlyK8sFiles: true})
+
+	k8sValidatorMock.AssertCalled(t, "ValidateResources", mock.Anything, 100)
+	k8sValidatorMock.AssertCalled(t, "GetK8sFiles", mock.Anything, 100)
+}
+
+func TestTestCommandNoInternetConnection(t *testing.T) {
+	setup()
+	_ = Test(ctx, []string{"valid/path"}, &TestCommandData{Policy: testingPolicy})
+
+	policyCheckData := evaluation.PolicyCheckData{
+		FilesConfigurations: filesConfigurations,
+		IsInteractiveMode:   true,
+		PolicyName:          testingPolicy.Name,
+		Policy:              testingPolicy,
+	}
+
+	path := "valid/path"
+	filesConfigurationsChan := newFilesConfigurationsChan(path)
+	invalidK8sFilesChan := newInvalidK8sFilesChan()
+	K8sValidationWarnings := validation.K8sValidationWarningPerValidFile{"valid/path": "Validation warning message - no internet"}
+
+	k8sValidatorMock.On("ValidateResources", mock.Anything, mock.Anything).Return(filesConfigurationsChan, invalidK8sFilesChan, K8sValidationWarnings, newErrorsChan())
+
+	k8sValidatorMock.AssertCalled(t, "ValidateResources", mock.Anything, 100)
+	mockedEvaluator.AssertCalled(t, "Evaluate", policyCheckData)
 }
 
 func executeTestCommand(ctx *TestCommandContext, args []string) error {
@@ -257,69 +352,6 @@ func test_testCommand_version_flags_validation(t *testing.T, ctx *TestCommandCon
 	assert.NoError(t, err)
 }
 
-func test_testCommand_no_flags(t *testing.T, evaluator *mockEvaluator, k8sValidator *K8sValidatorMock, filesConfigurations []*extractor.FileConfigurations, ctx *TestCommandContext, policy policy_factory.Policy) {
-	_ = Test(ctx, []string{"8/*"}, &TestCommandData{K8sVersion: "1.18.0", Output: "", Policy: policy, Token: "134kh"})
-
-	policyCheckData := evaluation.PolicyCheckData{
-		FilesConfigurations: filesConfigurations,
-		IsInteractiveMode:   true,
-		PolicyName:          policy.Name,
-		Policy:              policy,
-	}
-
-	k8sValidator.AssertCalled(t, "ValidateResources", mock.Anything, 100)
-	evaluator.AssertCalled(t, "Evaluate", policyCheckData)
-}
-
-func test_testCommand_json_output(t *testing.T, evaluator *mockEvaluator, k8sValidator *K8sValidatorMock, filesConfigurations []*extractor.FileConfigurations, ctx *TestCommandContext, policy policy_factory.Policy) {
-	_ = Test(ctx, []string{"8/*"}, &TestCommandData{Output: "json"})
-
-	policyCheckData := evaluation.PolicyCheckData{
-		FilesConfigurations: filesConfigurations,
-		IsInteractiveMode:   true,
-		PolicyName:          policy.Name,
-		Policy:              policy,
-	}
-
-	k8sValidator.AssertCalled(t, "ValidateResources", mock.Anything, 100)
-	evaluator.AssertCalled(t, "Evaluate", policyCheckData)
-}
-
-func test_testCommand_yaml_output(t *testing.T, evaluator *mockEvaluator, k8sValidator *K8sValidatorMock, filesConfigurations []*extractor.FileConfigurations, ctx *TestCommandContext, policy policy_factory.Policy) {
-	_ = Test(ctx, []string{"8/*"}, &TestCommandData{Output: "yaml"})
-
-	policyCheckData := evaluation.PolicyCheckData{
-		FilesConfigurations: filesConfigurations,
-		IsInteractiveMode:   true,
-		PolicyName:          policy.Name,
-		Policy:              policy,
-	}
-
-	k8sValidator.AssertCalled(t, "ValidateResources", mock.Anything, 100)
-	evaluator.AssertCalled(t, "Evaluate", policyCheckData)
-}
-
-func test_testCommand_xml_output(t *testing.T, evaluator *mockEvaluator, k8sValidator *K8sValidatorMock, filesConfigurations []*extractor.FileConfigurations, ctx *TestCommandContext, policy policy_factory.Policy) {
-	_ = Test(ctx, []string{"8/*"}, &TestCommandData{Output: "xml"})
-
-	policyCheckData := evaluation.PolicyCheckData{
-		FilesConfigurations: filesConfigurations,
-		IsInteractiveMode:   true,
-		PolicyName:          policy.Name,
-		Policy:              policy,
-	}
-
-	k8sValidator.AssertCalled(t, "ValidateResources", mock.Anything, 100)
-	evaluator.AssertCalled(t, "Evaluate", policyCheckData)
-}
-
-func test_testCommand_only_k8s_files(t *testing.T, k8sValidator *K8sValidatorMock, filesConfigurations []*extractor.FileConfigurations, evaluationId int, ctx *TestCommandContext) {
-	_ = Test(ctx, []string{"8/*"}, &TestCommandData{OnlyK8sFiles: true})
-
-	k8sValidator.AssertCalled(t, "ValidateResources", mock.Anything, 100)
-	k8sValidator.AssertCalled(t, "GetK8sFiles", mock.Anything, 100)
-}
-
 func newFilesConfigurationsChan(path string) chan *extractor.FileConfigurations {
 	filesConfigurationsChan := make(chan *extractor.FileConfigurations, 1)
 
@@ -371,6 +403,15 @@ func newIgnoredYamlFilesChan() chan *extractor.FileConfigurations {
 	return ignoredFilesChan
 }
 
+func newK8sValidationWarningsChan() chan *validation.FileWithWarning {
+	k8sValidationWarningsChan := make(chan *validation.FileWithWarning, 1)
+	go func() {
+		close(k8sValidationWarningsChan)
+	}()
+
+	return k8sValidationWarningsChan
+}
+
 func newErrorsChan() chan error {
 	invalidFilesChan := make(chan error, 1)
 
diff --git a/cmd/test/validationManager.go b/cmd/test/validationManager.go
index bd65406..6c992c9 100644
--- a/cmd/test/validationManager.go
+++ b/cmd/test/validationManager.go
@@ -1,14 +1,24 @@
 package test
 
 import (
+	"fmt"
+
+	"github.com/datreeio/datree/bl/validation"
 	"github.com/datreeio/datree/pkg/extractor"
 )
 
 type ValidationManager struct {
-	invalidYamlFiles            []*extractor.InvalidFile
-	invalidK8sFiles             []*extractor.InvalidFile
-	validK8sFilesConfigurations []*extractor.FileConfigurations
-	ignoredFiles                []extractor.FileConfigurations
+	invalidYamlFiles                 []*extractor.InvalidFile
+	invalidK8sFiles                  []*extractor.InvalidFile
+	validK8sFilesConfigurations      []*extractor.FileConfigurations
+	k8sValidationWarningPerValidFile validation.K8sValidationWarningPerValidFile
+	ignoredFiles                     []extractor.FileConfigurations
+}
+
+func NewValidationManager() *ValidationManager {
+	return &ValidationManager{
+		k8sValidationWarningPerValidFile: make(validation.K8sValidationWarningPerValidFile),
+	}
 }
 
 func (v *ValidationManager) AggregateInvalidYamlFiles(invalidFilesChan chan *extractor.InvalidFile) {
@@ -49,6 +59,24 @@ func (v *ValidationManager) ValidK8sFilesConfigurations() []*extractor.FileConfi
 	return v.validK8sFilesConfigurations
 }
 
+func (v *ValidationManager) GetK8sValidationSummaryStr(filesCount int) string {
+	if v.hasFilesWithWarnings() {
+		return "skipped since there is no internet connection"
+	}
+
+	return fmt.Sprintf("%v/%v", v.ValidK8sFilesConfigurationsCount(), filesCount)
+}
+
+func (v *ValidationManager) hasFilesWithWarnings() bool {
+	for _, value := range v.k8sValidationWarningPerValidFile {
+		if value != "" {
+			return true
+		}
+	}
+
+	return false
+}
+
 func (v *ValidationManager) ValidK8sFilesConfigurationsCount() int {
 	return len(v.validK8sFilesConfigurations)
 }
@@ -63,6 +91,16 @@ func (v *ValidationManager) ValidK8sConfigurationsCount() int {
 	return totalConfigs
 }
 
+func (v *ValidationManager) AggregateK8sValidationWarningsPerValidFile(filesWithWarningsChan chan *validation.FileWithWarning) {
+	for fileWithWarning := range filesWithWarningsChan {
+		v.k8sValidationWarningPerValidFile[fileWithWarning.Filename] = fileWithWarning.Warning
+	}
+}
+
+func (v *ValidationManager) GetK8sValidationWarningPerValidFile() validation.K8sValidationWarningPerValidFile {
+	return v.k8sValidationWarningPerValidFile
+}
+
 func (v *ValidationManager) AggregateIgnoredYamlFiles(ignoredFilesChan chan *extractor.FileConfigurations) {
 	for ignoredFile := range ignoredFilesChan {
 		v.ignoredFiles = append(v.ignoredFiles, *ignoredFile)
diff --git a/pkg/printer/printer_test.go b/pkg/printer/printer_test.go
index 7aaa528..83196a6 100644
--- a/pkg/printer/printer_test.go
+++ b/pkg/printer/printer_test.go
@@ -153,7 +153,7 @@ func TestPrintEvaluationSummary(t *testing.T) {
 			RulesCount:                21,
 			FilesCount:                5,
 			PassedYamlValidationCount: 4,
-			PassedK8sValidationCount:  3,
+			K8sValidation:             "3/5",
 			PassedPolicyCheckCount:    2,
 		}
 		k8sVersion := "1.2.3"
@@ -167,6 +167,36 @@ func TestPrintEvaluationSummary(t *testing.T) {
 
 - Passing policy check: 2/5
 
+`)
+
+		got := out.(*bytes.Buffer).Bytes()
+
+		assert.Equal(t, string(expected), string(got))
+
+	})
+
+	t.Run("Test PrintEvaluationSummary with no connection warning", func(t *testing.T) {
+		out = new(bytes.Buffer)
+		printer := CreateNewPrinter()
+		summary := EvaluationSummary{
+			ConfigsCount:              6,
+			RulesCount:                21,
+			FilesCount:                5,
+			PassedYamlValidationCount: 4,
+			K8sValidation:             "no internet connection",
+			PassedPolicyCheckCount:    2,
+		}
+		k8sVersion := "1.2.3"
+
+		printer.PrintEvaluationSummary(summary, k8sVersion)
+		expected := []byte(`(Summary)
+
+- Passing YAML validation: 4/5
+
+- Passing Kubernetes (1.2.3) schema validation: no internet connection
+
+- Passing policy check: 2/5
+
 `)
 
 		got := out.(*bytes.Buffer).Bytes()