Repository: datreeio/datree
Pull request #19 (2021-04-22 08:44:04)

staging


Patch:
diff --git a/README.md b/README.md
index 51e38c1..a95f22a 100644
--- a/README.md
+++ b/README.md
@@ -8,15 +8,14 @@
 ## What is Datree?
 [Datree](https://datree.io/?utm_source=github&utm_medium=organic_oss) helps to prevent Kubernetes misconfigurations from ever making it to production.  
 
-The CLI integration provides policies enforcement solution for Kubernetes to run automatic checks on every pull request for rule violations and misconfigurations. When rule violations are found, Datree prevents merging and shows the developer instructions to understand and fix the issue inside the CI process.  
+The CLI integration can be used locally or in the CI to provide a policy enforcement solution for Kubernetes. The policy runs automatic checks on every invocation (e.g. pull request, code change, etc.) for rule violations and misconfigurations. When rule violations are found, Datree will prevent them and will show the Engineers instructions on how and why to fix those issues.
 
 ## Quick start in two steps
 #### 1. Install the latest release on your CLI  
-**MacOS:** ``curl https://get.datree.io | /bin/sh``  
-**Linux:** ``curl https://get.datree.io/install-linux.sh | /bin/sh``  
+**Linux & MacOS:** ``curl https://get.datree.io | /bin/bash``  
 **Windows:** ``not supported yet :(``  
 
-#### 2. Pass datree a Kuberntes manifest file
+#### 2. Pass datree a Kuberntes manifest file to scan
 ``datree test <k8s-manifest-file>``  
 
 ...and voilà, you just made your first invocation! 🥳    
diff --git a/bl/evaluator.go b/bl/evaluator.go
index 26babd6..6c8724d 100644
--- a/bl/evaluator.go
+++ b/bl/evaluator.go
@@ -1,6 +1,7 @@
 package bl
 
 import (
+	"encoding/json"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -8,6 +9,7 @@ import (
 	"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 {
@@ -15,15 +17,17 @@ type Printer interface {
 	PrintSummaryTable(summary printer.Summary)
 }
 type CLIClient interface {
-	RequestEvaluation(pattern string, files []*propertiesExtractor.FileProperties, cliId string) (cliClient.EvaluationResponse, error)
+	RequestEvaluation(cliClient.EvaluationRequest) (cliClient.EvaluationResponse, error)
 }
 type PropertiesExtractor interface {
 	ReadFilesFromPattern(pattern 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 {
@@ -31,6 +35,7 @@ func CreateNewEvaluator(pe PropertiesExtractor, c CLIClient, p Printer) *Evaluat
 		propertiesExtractor: pe,
 		cliClient:           c,
 		printer:             p,
+		osInfo:              NewOsInfo(),
 	}
 }
 
@@ -43,7 +48,13 @@ type EvaluationResults struct {
 	}
 }
 
-func (e *Evaluator) Evaluate(pattern string, cliId string, evaluationConc int) (*EvaluationResults, []propertiesExtractor.FileError, error) {
+type UserAgent struct {
+	OS              string
+	PlatformVersion string
+	KernelVersion   string
+}
+
+func (e *Evaluator) Evaluate(pattern string, cliId string, evaluationConc int, cliVersion string) (*EvaluationResults, []propertiesExtractor.FileError, error) {
 	files, fileErrors, errors := e.propertiesExtractor.ReadFilesFromPattern(pattern, evaluationConc)
 	if len(errors) > 0 {
 		return nil, fileErrors, fmt.Errorf("failed evaluation with the following errors: %s", errors)
@@ -53,7 +64,25 @@ func (e *Evaluator) Evaluate(pattern string, cliId string, evaluationConc int) (
 		return nil, fileErrors, fmt.Errorf("no files detected")
 	}
 
-	res, err := e.cliClient.RequestEvaluation(pattern, files, cliId)
+	var filesProperties []propertiesExtractor.FileProperties
+
+	for _, file := range files {
+		filesProperties = append(filesProperties, *file)
+	}
+
+	evaluationRequest := cliClient.EvaluationRequest{
+		CliId:   cliId,
+		Pattern: pattern,
+		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
 	}
@@ -63,7 +92,29 @@ func (e *Evaluator) Evaluate(pattern string, cliId string, evaluationConc int) (
 	return results, fileErrors, nil
 }
 
-func (e *Evaluator) PrintResults(results *EvaluationResults, cliId string) error {
+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)
diff --git a/bl/osInfo.go b/bl/osInfo.go
new file mode 100644
index 0000000..4a5e39a
--- /dev/null
+++ b/bl/osInfo.go
@@ -0,0 +1,18 @@
+package bl
+
+import "github.com/shirou/gopsutil/host"
+
+type OSInfo struct {
+	OS              string
+	PlatformVersion string
+	KernelVersion   string
+}
+
+func NewOsInfo() *OSInfo {
+	infoStat, _ := host.Info()
+	return &OSInfo{
+		OS:              infoStat.OS,
+		KernelVersion:   infoStat.KernelVersion,
+		PlatformVersion: infoStat.PlatformVersion,
+	}
+}
diff --git a/go.mod b/go.mod
index 3506b48..b202560 100644
--- a/go.mod
+++ b/go.mod
@@ -3,16 +3,19 @@ module github.com/datreeio/datree
 go 1.15
 
 require (
+	github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect
 	github.com/bmatcuk/doublestar/v2 v2.0.4
 	github.com/briandowns/spinner v1.12.0
 	github.com/fatih/color v1.10.0
+	github.com/go-ole/go-ole v1.2.5 // indirect
 	github.com/google/uuid v1.2.0 // indirect
 	github.com/kyokomi/emoji v2.2.4+incompatible
 	github.com/lithammer/shortuuid v3.0.0+incompatible
-	github.com/magiconair/properties v1.8.1
 	github.com/olekukonko/tablewriter v0.0.5
+	github.com/shirou/gopsutil v3.21.3+incompatible
 	github.com/spf13/cobra v1.1.3
 	github.com/spf13/viper v1.7.1
 	github.com/stretchr/testify v1.7.0
+	github.com/tklauser/go-sysconf v0.3.5 // indirect
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
 )
diff --git a/go.sum b/go.sum
index 1246e4e..1eb0ce5 100644
--- a/go.sum
+++ b/go.sum
@@ -15,6 +15,8 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 h1:5sXbqlSomvdjlRbWyNqkPsJ3Fg+tQZCbgeX1VGljbQY=
+github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@@ -53,6 +55,8 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
+github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
@@ -177,6 +181,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/shirou/gopsutil v3.21.3+incompatible h1:uenXGGa8ESCQq+dbgtl916dmg6PSAz2cXov0uORQ9v8=
+github.com/shirou/gopsutil v3.21.3+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
@@ -208,6 +214,10 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
+github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4=
+github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
+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/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
@@ -276,9 +286,12 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc=
 golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 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-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=
diff --git a/install.sh b/install.sh
index 8cbb3a3..206b6cc 100755
--- a/install.sh
+++ b/install.sh
@@ -12,11 +12,14 @@ OUTPUT_BASENAME_WITH_POSTFIX=$OUTPUT_BASENAME.zip
 curl -L $DOWNLOAD_URL -o $OUTPUT_BASENAME_WITH_POSTFIX
 unzip $OUTPUT_BASENAME_WITH_POSTFIX -d $OUTPUT_BASENAME
 
+mkdir -p ~/.datree
+
 if [[ $osName == "Linux" ]]
 then
+    sudo rm -f /usr/local/bin/datree
     sudo cp $OUTPUT_BASENAME/datree /usr/local/bin
-    mkdir ~/.datree
 else
+    rm -f /usr/local/bin/datree
     cp $OUTPUT_BASENAME/datree /usr/local/bin
 fi
 
diff --git a/internal/fixtures/kube/k8s-demo.yaml b/internal/fixtures/kube/k8s-demo.yaml
index 8e0fbad..19f50a4 100644
--- a/internal/fixtures/kube/k8s-demo.yaml
+++ b/internal/fixtures/kube/k8s-demo.yaml
@@ -1,53 +1,14 @@
-apiVersion: extensions/v1beta1
-kind: Pod
-metadata:
-  name: static-web
-  namespace: test
-  labels:
-    role: .myrole
-spec:
-  serviceAccount: my-service-account
-  hostPID: true
-  hostIPC: true
-  hostNetwork: true
-  containers:
-    - name: web
-      image: nginx:latest
-      securityContext:
-        privileged: true
-        runAsUser: 2000
-      livenessProbe:
-        httpGet:
-          path: /health
-          port: 8080
-          httpHeaders:
-            - name: Custom-Header
-              value: Awesome
-      readinessProbe:
-        tcpSocket:
-          port: 8080
-        initialDelaySeconds: 5
-        periodSeconds: 10
-      resources:
-        requests:
-          cpu: "64Mi"
-        limits:
-          memory: "128Mi"
-      ports:
-        - name: web
-          containerPort: 80
-          protocol: TCP
----
-apiVersion: kubeadm.k8s.io/v1beta1
+apiVersion: apps/v1
 kind: Deployment
 metadata:
   name: rss-site
-  namespace: default
+  namespace: test
   labels:
-    owner: test@datree.io
+    owner: --
+    environment: prod
     app: web
 spec:
-  replicas: 1
+  replicas: 2
   selector:
     matchLabels:
       app: web
@@ -57,21 +18,9 @@ spec:
       labels:
         app: web
     spec:
-      restartPolicy: Never
-      volumes:
-        - name: docker-sock
-          hostPath:
-            path: /var/run/docker.sock
       containers:
         - name: front-end
-          image: nginx@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2
-          livenessProbe:
-            httpGet:
-              path: /healthz
-              port: 8080
-              httpHeaders:
-                - name: Custom-Header
-                  value: Awesome
+          image: nginx:latest
           readinessProbe:
             tcpSocket:
               port: 8080
@@ -79,22 +28,21 @@ spec:
             periodSeconds: 10
           resources:
             requests:
-              cpu: "64Mi"
+              memory: "64Mi"
+              cpu: "64m"
             limits:
-              memory: "128Mi"
+              cpu: "500m"
           ports:
             - containerPort: 80
         - name: rss-reader
-          image: nickchase/nginx@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2
-          securityContext:
-            runAsUser: 100
+          image: datree/nginx@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2
           livenessProbe:
             httpGet:
               path: /healthz
               port: 8080
               httpHeaders:
-                - name: Custom-Header
-                  value: Awesome
+              - name: Custom-Header
+                value: Awesome
           readinessProbe:
             tcpSocket:
               port: 8080
@@ -102,95 +50,10 @@ spec:
             periodSeconds: 10
           resources:
             requests:
-              memory: "64Mi"
-              cpu: "64Mi"
+              cpu: "64m"
+              memory: "128Mi"
             limits:
               memory: "128Mi"
               cpu: "500m"
           ports:
             - containerPort: 88
----
-apiVersion: networking.k8s.io/v1
-kind: Ingress
-metadata:
-  name: ingress-wildcard-host
-  labels:
-    owner: test@datree.io
-    environment: prod
-spec:
-  rules:
-    - host: "*"
-      http:
-        paths:
-          - pathType: Prefix
-            path: "/bar"
-            backend:
-              service:
-                name: service1
-                port:
-                  number: 80
----
-apiVersion: v1
-kind: Service
-metadata:
-  name: my-service
-  labels:
-    owner: test@datree.io
-spec:
-  type: NodePort
-  selector:
-    app: MyApp
-  ports:
-    - protocol: TCP
-      port: 80
-      targetPort: 9376
----
-apiVersion: batch/v1beta1
-kind: CronJob
-metadata:
-  name: hello
-  namespace: test
-  labels:
-    owner: test@datree.io
-spec:
-  schedule: "1 * * *"
-  jobTemplate:
-    spec:
-      template:
-        spec:
-          volumes:
-            - name: docker-sock
-              hostPath:
-                path: /var/run/docker
-          containers:
-            - name: hello
-              image: busybox
-              imagePullPolicy: IfNotPresent
-              command:
-                - /bin/sh
-                - -c
-                - date; echo Hello from the Kubernetes cluster
-          restartPolicy: OnFailure
----
-apiVersion: autoscaling/v2beta2
-kind: HorizontalPodAutoscaler
-metadata:
-  name: php-apache
-spec:
-  scaleTargetRef:
-    apiVersion: apps/v1
-    kind: Deployment
-    name: php-apache
-  metrics:
-    - type: Resource
-      resource:
-        name: cpu
-        target:
-          type: Utilization
-          averageUtilization: 50
----
-apiVersion: rbac.authorization.k8s.io/v1alpha1
-kind: InitConfiguration
----
-apiVersion: apps/v1beta2
-kind: Deployment
diff --git a/pkg/cliClient/cliClient.go b/pkg/cliClient/cliClient.go
index 019dd30..fe1eb80 100644
--- a/pkg/cliClient/cliClient.go
+++ b/pkg/cliClient/cliClient.go
@@ -3,7 +3,6 @@ package cliClient
 import (
 	"encoding/json"
 	"net/http"
-	"runtime"
 
 	"github.com/datreeio/datree/pkg/httpClient"
 	extractor "github.com/datreeio/datree/pkg/propertiesExtractor"
@@ -12,7 +11,6 @@ import (
 type HTTPClient interface {
 	Request(method string, resourceURI string, body interface{}, headers map[string]string) (httpClient.Response, error)
 }
-
 type CliClient struct {
 	baseUrl    string
 	httpClient HTTPClient
@@ -26,14 +24,18 @@ func NewCliClient(url string) *CliClient {
 	}
 }
 
+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"`
-	Pattern  string `json:"pattern"`
-	Metadata struct {
-		CliVersion string `json:"cliVersion"`
-		Os         string `json:"os"`
-	} `json:"metadata"`
-	Files []extractor.FileProperties `json:"files"`
+	CliId    string                     `json:"cliId"`
+	Pattern  string                     `json:"pattern"`
+	Metadata Metadata                   `json:"metadata"`
+	Files    []extractor.FileProperties `json:"files"`
 }
 
 type Match struct {
@@ -62,9 +64,8 @@ type EvaluationResponse struct {
 	Status       string             `json:"status"`
 }
 
-func (c *CliClient) RequestEvaluation(pattern string, files []*extractor.FileProperties, cliId string) (EvaluationResponse, error) {
-	evaluationRequest := c.createEvaluationRequest(pattern, files, cliId)
-	res, err := c.httpClient.Request(http.MethodPost, "/cli/evaluate", evaluationRequest, nil)
+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
 	}
@@ -77,24 +78,3 @@ func (c *CliClient) RequestEvaluation(pattern string, files []*extractor.FilePro
 
 	return *evaluationResponse, nil
 }
-
-func (c *CliClient) createEvaluationRequest(pattern string, files []*extractor.FileProperties, cliId string) EvaluationRequest {
-	var filesProperties []extractor.FileProperties
-
-	for _, file := range files {
-		filesProperties = append(filesProperties, *file)
-	}
-
-	return EvaluationRequest{
-		CliId:   cliId,
-		Pattern: pattern,
-		Metadata: struct {
-			CliVersion string "json:\"cliVersion\""
-			Os         string "json:\"os\""
-		}{
-			CliVersion: "0.0.1",
-			Os:         runtime.GOOS,
-		},
-		Files: filesProperties,
-	}
-}


Test patch:
diff --git a/bl/evaluator_test.go b/bl/evaluator_test.go
index 5bd6d35..1402de2 100644
--- a/bl/evaluator_test.go
+++ b/bl/evaluator_test.go
@@ -24,8 +24,8 @@ type mockCliClient struct {
 	mock.Mock
 }
 
-func (m *mockCliClient) RequestEvaluation(pattern string, files []*propertiesExtractor.FileProperties, cliId string) (cliClient.EvaluationResponse, error) {
-	args := m.Called(pattern, files, cliId)
+func (m *mockCliClient) RequestEvaluation(evaluationRequest cliClient.EvaluationRequest) (cliClient.EvaluationResponse, error) {
+	args := m.Called(evaluationRequest)
 	return args.Get(0).(cliClient.EvaluationResponse), args.Error(1)
 }
 
@@ -59,9 +59,10 @@ type cliClientMockTestCase struct {
 type evaluateTestCase struct {
 	name string
 	args struct {
-		pattern        string
-		cliId          string
-		evaluationConc int
+		pattern           string
+		cliId             string
+		evaluationConc    int
+		evaluationRequest cliClient.EvaluationRequest
 	}
 	mock struct {
 		propertiesExtractor propertiesExtractorMockTestCase
@@ -86,18 +87,23 @@ func TestEvaluate(t *testing.T) {
 			printer := &mockPrinter{}
 
 			propertiesExtractor.On("ReadFilesFromPattern", mock.Anything, mock.Anything).Return(tt.mock.propertiesExtractor.readFilesFromPattern.properties, tt.mock.propertiesExtractor.readFilesFromPattern.filesErrors, tt.mock.propertiesExtractor.readFilesFromPattern.errors)
-			cliClient.On("RequestEvaluation", mock.Anything, mock.Anything, mock.Anything).Return(tt.mock.cliClient.requestEvaluation.response, tt.mock.cliClient.requestEvaluation.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.pattern, tt.args.cliId, tt.args.evaluationConc)
+			actualResponse, actualFilesErrs, actualErr := evaluator.Evaluate(tt.args.pattern, tt.args.cliId, tt.args.evaluationConc, "0.0.1")
 
 			propertiesExtractor.AssertCalled(t, "ReadFilesFromPattern", tt.args.pattern, tt.args.evaluationConc)
-			cliClient.AssertCalled(t, "RequestEvaluation", tt.args.pattern, tt.mock.propertiesExtractor.readFilesFromPattern.properties, tt.args.cliId)
+			cliClient.AssertCalled(t, "RequestEvaluation", tt.args.evaluationRequest)
 
 			assert.Equal(t, tt.expected.response, actualResponse)
 			assert.Equal(t, tt.expected.fileErrors, actualFilesErrs)
@@ -125,14 +131,28 @@ func test_evaluate_success() *evaluateTestCase {
 	return &evaluateTestCase{
 		name: "success",
 		args: struct {
-			pattern        string
-			cliId          string
-			evaluationConc int
+			pattern           string
+			cliId             string
+			evaluationConc    int
+			evaluationRequest cliClient.EvaluationRequest
 		}{
 			pattern:        "*/*",
 			cliId:          "cliId-test",
 			evaluationConc: 1,
-		},
+			evaluationRequest: cliClient.EvaluationRequest{
+				Pattern: "*/*",
+				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
@@ -184,14 +204,28 @@ func test_evaluate_failedRequest() *evaluateTestCase {
 	return &evaluateTestCase{
 		name: "fail",
 		args: struct {
-			pattern        string
-			cliId          string
-			evaluationConc int
+			pattern           string
+			cliId             string
+			evaluationConc    int
+			evaluationRequest cliClient.EvaluationRequest
 		}{
 			pattern:        "*/*",
 			cliId:          "cliId-test",
 			evaluationConc: 1,
-		},
+			evaluationRequest: cliClient.EvaluationRequest{
+				Pattern: "*/*",
+				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
@@ -252,7 +286,7 @@ func TestPrintResults(t *testing.T) {
 		}{},
 	}
 
-	evaluator.PrintResults(&results, "cli_id")
+	evaluator.PrintResults(&results, "cli_id", "")
 
 	expectedPrinterWarnings := []printer.Warning{}
 	printerSpy.AssertCalled(t, "PrintWarnings", expectedPrinterWarnings)
diff --git a/cmd/test/main.go b/cmd/test/main.go
index e613f5c..f6b5343 100644
--- a/cmd/test/main.go
+++ b/cmd/test/main.go
@@ -17,9 +17,9 @@ type LocalConfigManager interface {
 }
 
 type Evaluator interface {
-	PrintResults(results *bl.EvaluationResults, cliId string) error
+	PrintResults(results *bl.EvaluationResults, cliId string, output string) error
 	PrintFileParsingErrors(errors []propertiesExtractor.FileError)
-	Evaluate(pattern string, cliId string, evaluationConc int) (*bl.EvaluationResults, []propertiesExtractor.FileError, error)
+	Evaluate(pattern string, cliId string, evaluationConc int, cliVersion string) (*bl.EvaluationResults, []propertiesExtractor.FileError, error)
 }
 
 type TestCommandContext struct {
@@ -27,21 +27,35 @@ type TestCommandContext struct {
 	Evaluator   Evaluator
 }
 
+type TestCommandFlags struct {
+	Output string
+}
+
 func CreateTestCommand(ctx *TestCommandContext) *cobra.Command {
-	return &cobra.Command{
+	testCommand := &cobra.Command{
 		Use:   "test",
 		Short: "Execute static analysis for pattern",
 		Long:  `Execute static analysis for pattern. Input should be glob`,
 		RunE: func(cmd *cobra.Command, args []string) error {
-			return test(ctx, args[0])
+			outputFlag, err := cmd.Flags().GetString("output")
+			if err != nil {
+				fmt.Println(err)
+				return err
+			}
+
+			testCommandFlags := TestCommandFlags{Output: outputFlag}
+			return test(ctx, args[0], testCommandFlags)
 		},
 		Args:          cobra.ExactValidArgs(1),
 		SilenceUsage:  true,
 		SilenceErrors: true,
 	}
+
+	testCommand.Flags().StringP("output", "o", "", "Define output format")
+	return testCommand
 }
 
-func test(ctx *TestCommandContext, pattern string) error {
+func test(ctx *TestCommandContext, pattern string, flags TestCommandFlags) error {
 	absolutePath, err := filepath.Abs(pattern)
 	if err != nil {
 		fmt.Println(err)
@@ -60,7 +74,7 @@ func test(ctx *TestCommandContext, pattern string) error {
 		return err
 	}
 
-	evaluationResponse, fileParsingErrors, err := ctx.Evaluator.Evaluate(absolutePath, config.CliId, 50)
+	evaluationResponse, fileParsingErrors, err := ctx.Evaluator.Evaluate(absolutePath, config.CliId, 50, "0.0.1")
 	s.Stop()
 
 	if err != nil {
@@ -77,5 +91,5 @@ func test(ctx *TestCommandContext, pattern string) error {
 
 	}
 
-	return ctx.Evaluator.PrintResults(evaluationResponse, config.CliId)
+	return ctx.Evaluator.PrintResults(evaluationResponse, config.CliId, flags.Output)
 }
diff --git a/cmd/test/main_test.go b/cmd/test/main_test.go
index e91ed3e..e069150 100644
--- a/cmd/test/main_test.go
+++ b/cmd/test/main_test.go
@@ -23,8 +23,8 @@ type mockEvaluator struct {
 	mock.Mock
 }
 
-func (m *mockEvaluator) PrintResults(results *bl.EvaluationResults, cliId string) error {
-	m.Called(results, cliId)
+func (m *mockEvaluator) PrintResults(results *bl.EvaluationResults, cliId string, output string) error {
+	m.Called(results, cliId, output)
 	return nil
 }
 
@@ -32,7 +32,7 @@ func (m *mockEvaluator) PrintFileParsingErrors(errors []propertiesExtractor.File
 	m.Called(errors)
 }
 
-func (m *mockEvaluator) Evaluate(pattern string, cliId string, evaluationConc int) (*bl.EvaluationResults, []propertiesExtractor.FileError, error) {
+func (m *mockEvaluator) Evaluate(pattern string, cliId string, evaluationConc int, cliVersion string) (*bl.EvaluationResults, []propertiesExtractor.FileError, error) {
 	args := m.Called(pattern, cliId, evaluationConc)
 	return args.Get(0).(*bl.EvaluationResults), args.Get(1).([]propertiesExtractor.FileError), args.Error(2)
 }
@@ -45,9 +45,9 @@ func TestTestCommand(t *testing.T) {
 			FilesCount       int
 		}{RulesCount: 1, TotalFailedRules: 0, FilesCount: 0},
 	}
-	evaluator.On("Evaluate", mock.Anything, mock.Anything, mock.Anything).Return(mockedEvaluateResponse, []propertiesExtractor.FileError{}, nil)
+	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).Return()
+	evaluator.On("PrintResults", mock.Anything, mock.Anything, mock.Anything).Return()
 
 	localConfigManager := &mockLocalConfigManager{}
 	localConfigManager.On("GetConfiguration").Return(localConfig.LocalConfiguration{CliId: "134kh"}, nil)
@@ -57,11 +57,37 @@ func TestTestCommand(t *testing.T) {
 		LocalConfig: localConfigManager,
 	}
 
-	test(ctx, "8/*")
+	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)
+}
+
+func test_testCommand_no_flags(t *testing.T, localConfigManager *mockLocalConfigManager, evaluator *mockEvaluator, mockedEvaluateResponse *bl.EvaluationResults, ctx *TestCommandContext) {
+	test(ctx, "8/*", TestCommandFlags{})
+	localConfigManager.AssertCalled(t, "GetConfiguration")
+
+	expectedPath, _ := filepath.Abs("8/*")
+	evaluator.AssertCalled(t, "Evaluate", expectedPath, "134kh", 50)
+	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) {
+	test(ctx, "8/*", TestCommandFlags{Output: "json"})
+	localConfigManager.AssertCalled(t, "GetConfiguration")
+
+	expectedPath, _ := filepath.Abs("8/*")
+	evaluator.AssertCalled(t, "Evaluate", expectedPath, "134kh", 50)
+	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) {
+	test(ctx, "8/*", TestCommandFlags{Output: "yaml"})
 	localConfigManager.AssertCalled(t, "GetConfiguration")
 
 	expectedPath, _ := filepath.Abs("8/*")
 	evaluator.AssertCalled(t, "Evaluate", expectedPath, "134kh", 50)
 	evaluator.AssertNotCalled(t, "PrintFileParsingErrors")
-	evaluator.AssertCalled(t, "PrintResults", mockedEvaluateResponse, "134kh")
+	evaluator.AssertCalled(t, "PrintResults", mockedEvaluateResponse, "134kh", "yaml")
 }
diff --git a/pkg/cliClient/cliClient_test.go b/pkg/cliClient/cliClient_test.go
index 2a1f135..bf6e210 100644
--- a/pkg/cliClient/cliClient_test.go
+++ b/pkg/cliClient/cliClient_test.go
@@ -5,7 +5,6 @@ import (
 	"io/ioutil"
 	"net/http"
 	"path/filepath"
-	"runtime"
 	"testing"
 
 	"github.com/datreeio/datree/pkg/httpClient"
@@ -27,9 +26,7 @@ func (c *mockHTTPClient) Request(method string, resourceURI string, body interfa
 type RequestEvaluationTestCase struct {
 	name string
 	args struct {
-		pattern    string
-		cliId      string
-		properties []*extractor.FileProperties
+		evaluationRequest *EvaluationRequest
 	}
 	mock struct {
 		response struct {
@@ -66,7 +63,7 @@ func TestRequestEvaluation(t *testing.T) {
 				httpClient: &httpClientMock,
 			}
 
-			res, _ := client.RequestEvaluation(tt.args.pattern, tt.args.properties, tt.args.cliId)
+			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)
@@ -118,13 +115,19 @@ func test_requestEvaluation_success() *RequestEvaluationTestCase {
 	return &RequestEvaluationTestCase{
 		name: "success - request evaluation",
 		args: struct {
-			pattern    string
-			cliId      string
-			properties []*extractor.FileProperties
+			evaluationRequest *EvaluationRequest
 		}{
-			pattern:    "pattern",
-			properties: castPropertiesPointersMock("service_mock", "mocks/service_mock.yaml"),
-			cliId:      "cli-id-test",
+			evaluationRequest: &EvaluationRequest{
+				CliId:   "cli-id-test",
+				Pattern: "pattern",
+				Files:   castPropertiesMock("service_mock", "mocks/service_mock.yaml"),
+				Metadata: Metadata{
+					CliVersion:      "0.0.1",
+					Os:              "darwin",
+					PlatformVersion: "1.2.3",
+					KernelVersion:   "4.5.6",
+				},
+			},
 		},
 		mock: struct {
 			response struct {
@@ -161,12 +164,11 @@ func test_requestEvaluation_success() *RequestEvaluationTestCase {
 					CliId:   "cli-id-test",
 					Pattern: "pattern",
 					Files:   castPropertiesMock("service_mock", "mocks/service_mock.yaml"),
-					Metadata: struct {
-						CliVersion string "json:\"cliVersion\""
-						Os         string "json:\"os\""
-					}{
-						CliVersion: "0.0.1",
-						Os:         runtime.GOOS,
+					Metadata: Metadata{
+						CliVersion:      "0.0.1",
+						Os:              "darwin",
+						PlatformVersion: "1.2.3",
+						KernelVersion:   "4.5.6",
 					},
 				},
 				headers: nil,