Repository: datreeio/datree
Pull request #135 (2021-08-15 13:52:07)

Add XML output support
Relates to #81.
Doesn't completely solve it since Azure DevOps expects the XML to be in JUnit format, but it takes the project one step closer towards that goal.


Patch:
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..72e8ffc
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,1 @@
+*
diff --git a/.goreleaser.yml b/.goreleaser.yml
index 05e44b7..02ce737 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -7,6 +7,18 @@ before:
   hooks:
     # you may remove this if you don't need go generate
     - go generate ./...
+brews: 
+  - name: datree
+    ids:
+    - datree-zip
+    tap:
+      owner: datreeio
+      name: ___TAP_NAME
+    install: bin.install "datree"
+    homepage: "https://datree.io/"
+    commit_author:
+      name: datree-ci
+      email: rnd@datree.io
 builds:
   - env:
       - CGO_ENABLED=0
@@ -39,6 +51,7 @@ archives:
       linux: Linux
       amd64: x86_64
     format: zip
+    id: datree-zip
 checksum:
   name_template: "checksums.txt"
 snapshot:
diff --git a/.travis.yml b/.travis.yml
index e21daa0..a718342 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,6 @@
 language: go
 go:
-  - 1.x
+  - 1.16.7
 os: osx
 osx_image: xcode12.5
 
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..97e3136
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,10 @@
+FROM ubuntu:18.04
+
+WORKDIR /go/src/app
+COPY . .
+
+RUN go get -d -v ./...
+RUN go build -tags main -ldflags="-X github.com/datreeio/datree/cmd.CliVersion=___CLI_VERSION" -v
+RUN go install -v ./...
+
+ENTRYPOINT ["/go/bin/datree"]
diff --git a/Makefile b/Makefile
index 85245e7..67cebef 100644
--- a/Makefile
+++ b/Makefile
@@ -15,3 +15,6 @@ print-version:
 
 set-token:
 	go run -tags=staging -ldflags="-X github.com/datreeio/datree/cmd.CliVersion=0.0.1" main.go config set token testtoken
+
+publish:
+	go run -tags=staging -ldflags="-X github.com/datreeio/datree/cmd.CliVersion=0.0.1" main.go publish ./internal/fixtures/policyAsCode/valid-schema.yaml
diff --git a/README.md b/README.md
index 86dbcb8..abbcdef 100644
--- a/README.md
+++ b/README.md
@@ -48,14 +48,14 @@ The rules cover different Kubernetes resources and use cases:
 * [Deprecation](https://hub.datree.io/deprecation/?utm_source=github&utm_medium=organic_oss)
 * [Others](https://hub.datree.io/other/?utm_source=github&utm_medium=organic_oss)
 
-## WARNING: Alpha release
+## Support
+
+[Datree](https://datree.io/?utm_source=github&utm_medium=organic_oss) builds and maintains this project to make Kubernetes policies simple and accessible. Start with our [documentations](https://hub.datree.io/?utm_source=github&utm_medium=organic_oss) for quick tutorials and examples.
+
+## Disclaimer
 
 Datree is at an early stage of development. We do our best to maintain backwards compatibility but there may be breaking changes in
 the future to the command usage, flags, and configuration file formats. The CLI will output a warning message when a new version with breaking changes is detected.
 We encourage you to use Datree to test your Kubernetes manifests files and Helm charts, see what
 breaks, and [contribute](./CONTRIBUTING.md).
 
-## Support
-
-[Datree](https://datree.io/?utm_source=github&utm_medium=organic_oss) builds and maintains this project to make Kubernetes policies simple and accessible. Start with our [documentations](https://hub.datree.io/?utm_source=github&utm_medium=organic_oss) for quick tutorials and examples.
-
diff --git a/bl/evaluation/evaluator.go b/bl/evaluation/evaluator.go
index 169d6f3..45f497a 100644
--- a/bl/evaluation/evaluator.go
+++ b/bl/evaluation/evaluator.go
@@ -25,8 +25,10 @@ func New(c CLIClient) *Evaluator {
 	}
 }
 
+type FileNameRuleMapper map[string]map[int]*Rule 
+
 type EvaluationResults struct {
-	FileNameRuleMapper map[string]map[int]*Rule
+	FileNameRuleMapper FileNameRuleMapper
 	Summary            struct {
 		TotalFailedRules int
 		FilesCount       int
diff --git a/bl/evaluation/printer.go b/bl/evaluation/printer.go
index 8aec229..3395be8 100644
--- a/bl/evaluation/printer.go
+++ b/bl/evaluation/printer.go
@@ -2,6 +2,7 @@ package evaluation
 
 import (
 	"encoding/json"
+	"encoding/xml"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -33,6 +34,8 @@ func PrintResults(results *EvaluationResults, invalidYamlFiles []*validation.Inv
 		return jsonOutput(&FormattedOutput{EvaluationResults: results, EvaluationSummary: evaluationSummary, InvalidYamlFiles: invalidYamlFiles, InvalidK8sFiles: invalidK8sFiles})
 	case outputFormat == "yaml":
 		return yamlOutput(&FormattedOutput{EvaluationResults: results, EvaluationSummary: evaluationSummary, InvalidYamlFiles: invalidYamlFiles, InvalidK8sFiles: invalidK8sFiles})
+	case outputFormat == "xml":
+		return xmlOutput(&FormattedOutput{EvaluationResults: results, EvaluationSummary: evaluationSummary, InvalidYamlFiles: invalidYamlFiles, InvalidK8sFiles: invalidK8sFiles})
 	default:
 		return textOutput(results, invalidYamlFiles, invalidK8sFiles, evaluationSummary, loginURL, printer, k8sVersion, policyName)
 	}
@@ -60,6 +63,18 @@ func yamlOutput(formattedOutput *FormattedOutput) error {
 	return nil
 }
 
+func xmlOutput(formmattedOutput *FormattedOutput) error {
+	xmlOutput, err := xml.MarshalIndent(formmattedOutput, "", "\t")
+	xmlOutput = []byte(xml.Header + string(xmlOutput))
+	if err != nil {
+		fmt.Println(err)
+		return err
+	}
+
+	fmt.Println(string(xmlOutput))
+	return nil
+}
+
 func textOutput(results *EvaluationResults, invalidYamlFiles []*validation.InvalidYamlFile, invalidK8sFiles []*validation.InvalidK8sFile, evaluationSummary printer.EvaluationSummary, url string, printer Printer, k8sVersion string, policyName string) error {
 	pwd, err := os.Getwd()
 	if err != nil {
@@ -227,3 +242,39 @@ func parseEvaluationResultsToSummary(results *EvaluationResults, evaluationSumma
 	}
 	return *summary
 }
+
+func (mapper FileNameRuleMapper) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+	if len(mapper) == 0 {
+		return nil
+	}
+
+	tokens := []xml.Token{start}
+	fileXMLName := xml.Name{Space: "", Local: "File"}
+	filenameXMLName := xml.Name{Space: "", Local: "filename"}
+
+	// Iterate over mapper and create XML tokens for all entries
+	for filePath, encapsulatedRule := range mapper {
+		for _, rule := range encapsulatedRule {	// Since rule is encapsulated by its id (int), we don't add is a tag
+			startToken := xml.StartElement{Name: fileXMLName, Attr: []xml.Attr{{Name: filenameXMLName, Value: filePath}}}
+			endToken := xml.EndElement{Name: fileXMLName}
+			tokens = append(tokens, startToken, rule, endToken)
+		}
+	}
+
+	tokens = append(tokens, xml.EndElement{Name: start.Name})
+
+	for _, t := range tokens {
+		var err error
+		switch t.(type) {
+		default:
+			err = e.EncodeToken(t)
+		case *Rule:
+			err = e.EncodeElement(t, xml.StartElement{Name: xml.Name{Space: "", Local: "Rule"}})
+		}
+		if err != nil {
+			return err
+		}
+	}
+
+	return e.Flush()
+}
diff --git a/bl/files/files.go b/bl/files/files.go
index b14c011..6e82aac 100644
--- a/bl/files/files.go
+++ b/bl/files/files.go
@@ -1,7 +1,10 @@
 package files
 
 import (
+	"bytes"
 	"fmt"
+	"gopkg.in/yaml.v3"
+	"io/ioutil"
 	"os"
 	"path/filepath"
 
@@ -9,6 +12,8 @@ import (
 	"github.com/datreeio/datree/pkg/extractor"
 )
 
+type UnknownStruct map[string]interface{}
+
 func ToAbsolutePath(path string) (string, error) {
 	absolutePath, err := filepath.Abs(path)
 	if err != nil {
@@ -59,3 +64,25 @@ func ExtractFilesConfigurations(paths []string, concurrency int) (chan *extracto
 
 	return filesConfigurationsChan, invalidFilesChan
 }
+
+func ExtractYamlFileToUnknownStruct(path string) (UnknownStruct, error) {
+	absolutePath, err := ToAbsolutePath(path)
+	if err != nil {
+		return nil, err
+	}
+
+	yamlContent, err := ioutil.ReadFile(absolutePath)
+	if err != nil {
+		return nil, err
+	}
+
+	yamlDecoder := yaml.NewDecoder(bytes.NewReader(yamlContent))
+	var policies = UnknownStruct{}
+	err = yamlDecoder.Decode(&policies)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return policies, nil
+}
diff --git a/cmd/publish/main.go b/cmd/publish/main.go
new file mode 100644
index 0000000..60475f5
--- /dev/null
+++ b/cmd/publish/main.go
@@ -0,0 +1,96 @@
+package publish
+
+import (
+	"fmt"
+	"github.com/datreeio/datree/bl/files"
+	"github.com/datreeio/datree/bl/messager"
+	"github.com/datreeio/datree/pkg/cliClient"
+	"github.com/datreeio/datree/pkg/localConfig"
+	"github.com/spf13/cobra"
+)
+
+type Messager interface {
+	LoadVersionMessages(messages chan *messager.VersionMessage, cliVersion string)
+}
+
+type Printer interface {
+	PrintMessage(messageText string, messageColor string)
+}
+
+type LocalConfig interface {
+	GetLocalConfiguration() (*localConfig.ConfigContent, error)
+}
+
+type CliClient interface {
+	PublishPolicies(policiesConfiguration files.UnknownStruct, cliId string) error
+}
+
+type PublishCommandContext struct {
+	CliVersion       string
+	LocalConfig      LocalConfig
+	Messager         Messager
+	Printer          Printer
+	PublishCliClient CliClient
+}
+
+func New(ctx *PublishCommandContext) *cobra.Command {
+	publishCommand := &cobra.Command{
+		Use:   "publish <fileName>",
+		Short: "Publish policies configuration for given <fileName>.",
+		Long:  "Publish policies configuration for given <fileName>. Input should be the path to the Policy-as-Code yaml configuration file",
+		Args: func(cmd *cobra.Command, args []string) error {
+			if len(args) != 1 {
+				errMessage := "Requires 1 arg\n"
+				cmd.Usage()
+				return fmt.Errorf(errMessage)
+			}
+			return nil
+		},
+		RunE: func(cmd *cobra.Command, args []string) error {
+			messages := make(chan *messager.VersionMessage, 1)
+			go ctx.Messager.LoadVersionMessages(messages, ctx.CliVersion)
+			defer func() {
+				msg, ok := <-messages
+				if ok {
+					ctx.Printer.PrintMessage(msg.MessageText+"\n", msg.MessageColor)
+				}
+			}()
+
+			err := publish(ctx, args[0])
+			if err != nil {
+				ctx.Printer.PrintMessage("Publish failed:\n"+err.Error()+"\n", "error")
+			} else {
+				ctx.Printer.PrintMessage("Published successfully\n", "green")
+			}
+
+			return err
+		},
+		SilenceUsage:  true,
+		SilenceErrors: true,
+	}
+
+	return publishCommand
+}
+
+type MessagesContext struct {
+	CliVersion  string
+	LocalConfig LocalConfig
+	Messager    Messager
+	Printer     Printer
+	CliClient   *cliClient.CliClient
+}
+
+func publish(ctx *PublishCommandContext, path string) error {
+	localConfigContent, err := ctx.LocalConfig.GetLocalConfiguration()
+	if err != nil {
+		return err
+	}
+
+	policiesConfiguration, err := files.ExtractYamlFileToUnknownStruct(path)
+	if err != nil {
+		return err
+	}
+
+	err = ctx.PublishCliClient.PublishPolicies(policiesConfiguration, localConfigContent.CliId)
+	return err
+}
diff --git a/cmd/root.go b/cmd/root.go
index 0a4a27c..e96bb97 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -5,6 +5,7 @@ import (
 	"github.com/datreeio/datree/bl/messager"
 	"github.com/datreeio/datree/bl/validation"
 	"github.com/datreeio/datree/cmd/config"
+	"github.com/datreeio/datree/cmd/publish"
 	"github.com/datreeio/datree/cmd/test"
 	"github.com/datreeio/datree/cmd/version"
 	"github.com/datreeio/datree/pkg/cliClient"
@@ -48,6 +49,14 @@ func init() {
 		Printer:     app.context.Printer,
 		LocalConfig: app.context.LocalConfig,
 	}))
+
+	rootCmd.AddCommand(publish.New(&publish.PublishCommandContext{
+		CliVersion:       CliVersion,
+		LocalConfig:      app.context.LocalConfig,
+		Messager:         app.context.Messager,
+		Printer:          app.context.Printer,
+		PublishCliClient: app.context.CliClient,
+	}))
 }
 
 func Execute() error {
diff --git a/go.mod b/go.mod
index 4ddef41..4b06b50 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,6 @@ go 1.15
 
 require (
 	github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect
-	github.com/aws/aws-sdk-go v1.38.40
 	github.com/bmatcuk/doublestar/v2 v2.0.4
 	github.com/briandowns/spinner v1.12.0
 	github.com/fatih/color v1.10.0
@@ -18,9 +17,8 @@ require (
 	github.com/spf13/viper v1.7.1
 	github.com/stretchr/testify v1.7.0
 	github.com/tklauser/go-sysconf v0.3.5 // indirect
-	github.com/yannh/kubeconform v0.4.7
-	golang.org/x/text v0.3.3
-	golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc
+	github.com/yannh/kubeconform v0.4.8
+	golang.org/x/text v0.3.3 // indirect
 	gopkg.in/yaml.v2 v2.4.0
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
 )
diff --git a/go.sum b/go.sum
index 9d61a06..7cd3ead 100644
--- a/go.sum
+++ b/go.sum
@@ -22,8 +22,6 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
-github.com/aws/aws-sdk-go v1.38.40 h1:VVqBFV24tGgXR11tFXPjmR+0ItbnUepbuQjdmhgu3U0=
-github.com/aws/aws-sdk-go v1.38.40/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -41,13 +39,11 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
-github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
-github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
 github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
@@ -112,9 +108,6 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
-github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
-github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
-github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
@@ -137,12 +130,10 @@ github.com/lithammer/shortuuid v3.0.0+incompatible/go.mod h1:FR74pbAuElzOUuenUHT
 github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
 github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
-github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
 github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
-github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
@@ -170,7 +161,6 @@ github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
@@ -233,8 +223,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:
 github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
-github.com/yannh/kubeconform v0.4.7 h1:ExAjZYd6D0WnG/Eq/IRxvTebPbARh6e6M96Pq8Xy5u0=
-github.com/yannh/kubeconform v0.4.7/go.mod h1:lhkEiaDOtSewHGGZ8iR2iiTC0CSnR7xbMEtyL4bm4rE=
+github.com/yannh/kubeconform v0.4.8 h1:xzxVzYJE1qTZkCQE0qo/4Y5LQ1ahCW51rrT4YZaOYjQ=
+github.com/yannh/kubeconform v0.4.8/go.mod h1:lhkEiaDOtSewHGGZ8iR2iiTC0CSnR7xbMEtyL4bm4rE=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
@@ -246,7 +236,6 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -279,7 +268,6 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -301,18 +289,14 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa h1:ZYxPR6aca/uhfRJyaOAtflSHjJYiktO7QnJC5ut7iY4=
 golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -334,7 +318,6 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw
 golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8=
 golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
diff --git a/install.sh b/install.sh
index 2f0d356..29f34ca 100755
--- a/install.sh
+++ b/install.sh
@@ -19,14 +19,8 @@ unzip -qq $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
-else
-    rm -f /usr/local/bin/datree || sudo rm -f /usr/local/bin/datree
-    cp $OUTPUT_BASENAME/datree /usr/local/bin || sudo cp $OUTPUT_BASENAME/datree /usr/local/bin
-fi
+rm -f /usr/local/bin/datree || sudo rm -f /usr/local/bin/datree
+cp $OUTPUT_BASENAME/datree /usr/local/bin || sudo cp $OUTPUT_BASENAME/datree /usr/local/bin
 
 rm $OUTPUT_BASENAME_WITH_POSTFIX
 rm -rf $OUTPUT_BASENAME
diff --git a/internal/fixtures/policyAsCode/invalid-schemas/duplicate-policy-name.yaml b/internal/fixtures/policyAsCode/invalid-schemas/duplicate-policy-name.yaml
new file mode 100644
index 0000000..8f9a0fd
--- /dev/null
+++ b/internal/fixtures/policyAsCode/invalid-schemas/duplicate-policy-name.yaml
@@ -0,0 +1,5 @@
+apiVersion: v1
+policies:
+  - name: "policy1"
+    isDefault: true
+  - name: 'policy1'
diff --git a/internal/fixtures/policyAsCode/invalid-schemas/duplicate-rule-id.yaml b/internal/fixtures/policyAsCode/invalid-schemas/duplicate-rule-id.yaml
new file mode 100644
index 0000000..1e1cfb5
--- /dev/null
+++ b/internal/fixtures/policyAsCode/invalid-schemas/duplicate-rule-id.yaml
@@ -0,0 +1,9 @@
+apiVersion: v1
+policies:
+  - name: "Default"
+    isDefault: true
+    rules:
+      - identifier: "CONTAINERS_MISSING_MEMORY_LIMIT_KEY"
+        failSuggestion: "This is the fail suggestion"
+      - identifier: "CONTAINERS_MISSING_MEMORY_LIMIT_KEY"
+        failSuggestion: "This is the fail suggestion"
diff --git a/internal/fixtures/policyAsCode/invalid-schemas/missing-default-policy.yaml b/internal/fixtures/policyAsCode/invalid-schemas/missing-default-policy.yaml
new file mode 100644
index 0000000..973d4ed
--- /dev/null
+++ b/internal/fixtures/policyAsCode/invalid-schemas/missing-default-policy.yaml
@@ -0,0 +1,6 @@
+apiVersion: v1
+policies:
+  - name: "Default"
+    rules:
+      - identifier: "CONTAINERS_MISSING_MEMORY_LIMIT_KEY"
+        failSuggestion: "This is the fail suggestion"
diff --git a/internal/fixtures/policyAsCode/invalid-schemas/multiple-default-policies.yaml b/internal/fixtures/policyAsCode/invalid-schemas/multiple-default-policies.yaml
new file mode 100644
index 0000000..f1d6eb8
--- /dev/null
+++ b/internal/fixtures/policyAsCode/invalid-schemas/multiple-default-policies.yaml
@@ -0,0 +1,6 @@
+apiVersion: v1
+policies:
+  - name: "Default"
+    isDefault: true
+  - name: 'otherDefaultPolicy'
+    isDefault: true
diff --git a/internal/fixtures/policyAsCode/invalid-schemas/unknown-rule-id.yaml b/internal/fixtures/policyAsCode/invalid-schemas/unknown-rule-id.yaml
new file mode 100644
index 0000000..ceeba3d
--- /dev/null
+++ b/internal/fixtures/policyAsCode/invalid-schemas/unknown-rule-id.yaml
@@ -0,0 +1,7 @@
+apiVersion: v1
+policies:
+  - name: "Default"
+    isDefault: true
+    rules:
+      - identifier: "CONTAINERS_MISSING_MEMORY_LIMIT_KEYaa"
+        failSuggestion: "This is the fail suggestion"
diff --git a/internal/fixtures/policyAsCode/invalid-schemas/wrong-api-version.yaml b/internal/fixtures/policyAsCode/invalid-schemas/wrong-api-version.yaml
new file mode 100644
index 0000000..0e3ffe0
--- /dev/null
+++ b/internal/fixtures/policyAsCode/invalid-schemas/wrong-api-version.yaml
@@ -0,0 +1,7 @@
+apiVersion: v2
+policies:
+  - name: "Default"
+    isDefault: true
+    rules:
+      - identifier: "CONTAINERS_MISSING_MEMORY_LIMIT_KEY"
+        failSuggestion: "This is the fail suggestion"
diff --git a/internal/fixtures/policyAsCode/invalid-yaml.yaml b/internal/fixtures/policyAsCode/invalid-yaml.yaml
new file mode 100644
index 0000000..28f9de5
--- /dev/null
+++ b/internal/fixtures/policyAsCode/invalid-yaml.yaml
@@ -0,0 +1,3 @@
+apiVersion: v1
+policies:
+  - name: "Default"a
diff --git a/internal/fixtures/policyAsCode/valid-schema.yaml b/internal/fixtures/policyAsCode/valid-schema.yaml
new file mode 100644
index 0000000..90a65ba
--- /dev/null
+++ b/internal/fixtures/policyAsCode/valid-schema.yaml
@@ -0,0 +1,14 @@
+apiVersion: v1
+policies:
+  - name: "Default"
+    isDefault: true
+    rules:
+      - identifier: "CONTAINERS_MISSING_MEMORY_LIMIT_KEY"
+        failSuggestion: "This is the fail suggestion"
+      - identifier: "CONTAINERS_MISSING_MEMORY_REQUEST_KEY"
+        failSuggestion: "This is the fail suggestion"
+  - name: 'allRulesAreDisabledPolicy' # all rules are disabled
+  - name: 'otherPolicy'
+    rules:
+      - identifier: 'INGRESS_INCORRECT_HOST_VALUE_PERMISSIVE'
+        failSuggestion: ''
diff --git a/internal/release/deploy.sh b/internal/release/deploy.sh
index 7daefe5..cc4c4cd 100644
--- a/internal/release/deploy.sh
+++ b/internal/release/deploy.sh
@@ -1,10 +1,17 @@
 set -ex
 if [ $TRAVIS_BRANCH == "main" ]; then 
   export DATREE_BUILD_VERSION=$SEMVER_NUMBER; 
+  export DATREE_BREW_REPO_NAME=homebrew-datree
 else 
   export DATREE_BUILD_VERSION=$SEMVER_NUMBER-$TRAVIS_BRANCH; 
+  export DATREE_BREW_REPO_NAME=homebrew-datree-staging
 fi
 
+sed -ie "s/___TAP_NAME/$DATREE_BREW_REPO_NAME/" .goreleaser.yml
+sed -ie "s/___CLI_VERSION/$DATREE_BUILD_VERSION/" Dockerfile
+git add .goreleaser.yml Dockerfile
+git commit -m "ci: dynamic variable updates"
+
 export GIT_TAG=$DATREE_BUILD_VERSION
 git tag $GIT_TAG -a -m "Generated tag from TravisCI for build $TRAVIS_BUILD_NUMBER"
 git push --tags
diff --git a/pkg/cliClient/publish.go b/pkg/cliClient/publish.go
new file mode 100644
index 0000000..3ff11d0
--- /dev/null
+++ b/pkg/cliClient/publish.go
@@ -0,0 +1,11 @@
+package cliClient
+
+import (
+	"github.com/datreeio/datree/bl/files"
+	"net/http"
+)
+
+func (c *CliClient) PublishPolicies(policiesConfiguration files.UnknownStruct, cliId string) error {
+	_, err := c.httpClient.Request(http.MethodPut, "/cli/policy/publish/cliIds/"+cliId, policiesConfiguration, nil)
+	return err
+}
diff --git a/pkg/extractor/extractor.go b/pkg/extractor/extractor.go
index dbe9b4f..2218831 100644
--- a/pkg/extractor/extractor.go
+++ b/pkg/extractor/extractor.go
@@ -24,7 +24,6 @@ func ParseYaml(content string) (*[]Configuration, error) {
 	if err != nil {
 		return nil, err
 	} else {
-
 		return configurations, nil
 	}
 }


Test patch:
diff --git a/bl/files/files_test.go b/bl/files/files_test.go
index 1247e8d..ca7d529 100644
--- a/bl/files/files_test.go
+++ b/bl/files/files_test.go
@@ -1,6 +1,7 @@
 package files
 
 import (
+	"errors"
 	"path/filepath"
 	"testing"
 
@@ -29,7 +30,6 @@ func TestToAbsolutePath(t *testing.T) {
 			assert.Equal(t, tt.expected.path, absolutePath)
 		})
 	}
-
 }
 
 func test_existed_file() *toAbsolutePathsTestCase {
@@ -73,3 +73,18 @@ func test_directory_file() *toAbsolutePathsTestCase {
 		},
 	}
 }
+
+func TestExtractYamlFileToUnknownStruct(t *testing.T) {
+	t.Run("valid yaml file, should return an unknown struct and no error", func(t *testing.T) {
+		actualResult, actualErr := ExtractYamlFileToUnknownStruct("../../internal/fixtures/policyAsCode/valid-schema.yaml")
+		assert.NotEqual(t, nil, actualResult)
+		assert.Equal(t, nil, actualErr)
+	})
+
+	t.Run("invalid yaml file, should return an error", func(t *testing.T) {
+		actualResult, actualErr := ExtractYamlFileToUnknownStruct("../../internal/fixtures/policyAsCode/invalid-yaml.yaml")
+		assert.Equal(t, UnknownStruct(nil), actualResult)
+		assert.NotEqual(t, nil, actualErr)
+		assert.Equal(t, errors.New("yaml: line 2: did not find expected key"), actualErr)
+	})
+}
diff --git a/cmd/publish/main_test.go b/cmd/publish/main_test.go
new file mode 100644
index 0000000..ddbf60d
--- /dev/null
+++ b/cmd/publish/main_test.go
@@ -0,0 +1,96 @@
+package publish
+
+import (
+	"errors"
+	"github.com/datreeio/datree/bl/files"
+	"github.com/datreeio/datree/bl/messager"
+	"github.com/datreeio/datree/pkg/localConfig"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+	"testing"
+)
+
+type LocalConfigMock struct {
+	mock.Mock
+}
+
+func (lc *LocalConfigMock) GetLocalConfiguration() (*localConfig.ConfigContent, error) {
+	lc.Called()
+	return &localConfig.ConfigContent{CliId: "cli_id"}, nil
+}
+
+type MessagerMock struct {
+	mock.Mock
+}
+
+func (m *MessagerMock) LoadVersionMessages(messages chan *messager.VersionMessage, cliVersion string) {
+	go func() {
+		messages <- &messager.VersionMessage{
+			CliVersion:   "1.2.3",
+			MessageText:  "version message mock",
+			MessageColor: "green"}
+		close(messages)
+	}()
+
+	m.Called(messages, cliVersion)
+}
+
+type PrinterMock struct {
+	mock.Mock
+}
+
+func (p *PrinterMock) PrintMessage(messageText string, messageColor string) {
+	p.Called(messageText, messageColor)
+}
+
+type PublishClientMock struct {
+	mock.Mock
+}
+
+func (p *PublishClientMock) PublishPolicies(policiesConfiguration files.UnknownStruct, cliId string) error {
+	return p.Called(policiesConfiguration, cliId).Error(0)
+}
+
+func TestPublishCommand(t *testing.T) {
+	localConfigMock := &LocalConfigMock{}
+	localConfigMock.On("GetLocalConfiguration")
+
+	messagerMock := &MessagerMock{}
+	messagerMock.On("LoadVersionMessages", mock.Anything, mock.Anything)
+
+	printerMock := &PrinterMock{}
+	printerMock.On("PrintMessage", mock.Anything, mock.Anything)
+
+	publishClientMock := &PublishClientMock{}
+
+	ctx := &PublishCommandContext{
+		CliVersion:       "0.0.1",
+		LocalConfig:      localConfigMock,
+		Messager:         messagerMock,
+		Printer:          printerMock,
+		PublishCliClient: publishClientMock,
+	}
+
+	testPublishCommandSuccess(t, ctx, publishClientMock)
+	testPublishCommandFailedYaml(t, ctx)
+	testPublishCommandFailedSchema(t, ctx, publishClientMock)
+}
+
+func testPublishCommandSuccess(t *testing.T, ctx *PublishCommandContext, publishClientMock *PublishClientMock) {
+	publishClientMock.On("PublishPolicies", mock.Anything, mock.Anything).Return(nil).Once()
+	err := publish(ctx, "../../internal/fixtures/policyAsCode/valid-schema.yaml")
+	assert.Equal(t, nil, err)
+}
+
+func testPublishCommandFailedYaml(t *testing.T, ctx *PublishCommandContext) {
+	err := publish(ctx, "../../internal/fixtures/policyAsCode/invalid-yaml.yaml")
+	assert.NotEqual(t, nil, err)
+	assert.Equal(t, "yaml: line 2: did not find expected key", err.Error())
+}
+
+func testPublishCommandFailedSchema(t *testing.T, ctx *PublishCommandContext, publishClientMock *PublishClientMock) {
+	publishClientMock.On("PublishPolicies", mock.Anything, mock.Anything).Return(errors.New("some error")).Once()
+	err := publish(ctx, "../../internal/fixtures/policyAsCode/invalid-schemas/duplicate-rule-id.yaml")
+	assert.NotEqual(t, nil, err)
+	assert.Equal(t, "some error", err.Error())
+}
diff --git a/cmd/test/main.go b/cmd/test/main.go
index d725608..7b826d6 100644
--- a/cmd/test/main.go
+++ b/cmd/test/main.go
@@ -154,7 +154,7 @@ func test(ctx *TestCommandContext, paths []string, flags TestCommandFlags) error
 	if flags.Output == "simple" {
 		ctx.Printer.SetTheme(printer.CreateSimpleTheme())
 	}
-	isInteractiveMode := (flags.Output != "json") && (flags.Output != "yaml")
+	isInteractiveMode := (flags.Output != "json") && (flags.Output != "yaml") && (flags.Output != "xml")
 
 	if isInteractiveMode == true {
 		messages := make(chan *messager.VersionMessage, 1)
diff --git a/cmd/test/main_test.go b/cmd/test/main_test.go
index c016dd2..ae6e38c 100644
--- a/cmd/test/main_test.go
+++ b/cmd/test/main_test.go
@@ -170,6 +170,7 @@ func TestTestCommand(t *testing.T) {
 	test_testCommand_no_flags(t, mockedEvaluator, filesConfigurations, evaluationId, ctx)
 	test_testCommand_json_output(t, mockedEvaluator, filesConfigurations, evaluationId, ctx)
 	test_testCommand_yaml_output(t, mockedEvaluator, filesConfigurations, evaluationId, ctx)
+	test_testCommand_xml_output(t, mockedEvaluator, filesConfigurations, evaluationId, ctx)
 }
 
 func test_testCommand_no_flags(t *testing.T, evaluator *mockEvaluator, filesConfigurations []*extractor.FileConfigurations, evaluationId int, ctx *TestCommandContext) {
@@ -191,6 +192,12 @@ func test_testCommand_yaml_output(t *testing.T, evaluator *mockEvaluator, filesC
 	evaluator.AssertCalled(t, "Evaluate", filesConfigurations, evaluationId)
 }
 
+func test_testCommand_xml_output(t *testing.T, evaluator *mockEvaluator, filesConfigurations []*extractor.FileConfigurations, evaluationId int, ctx *TestCommandContext) {
+	test(ctx, []string{"8/*"}, TestCommandFlags{Output: "xml"})
+
+	evaluator.AssertCalled(t, "Evaluate", filesConfigurations, evaluationId)
+}
+
 func newFilesConfigurationsChan(path string) chan *extractor.FileConfigurations {
 	filesConfigurationsChan := make(chan *extractor.FileConfigurations, 1)
 
diff --git a/pkg/cliClient/cliClient_test.go b/pkg/cliClient/cliClient_test.go
index b138a3f..ab676c7 100644
--- a/pkg/cliClient/cliClient_test.go
+++ b/pkg/cliClient/cliClient_test.go
@@ -3,6 +3,8 @@ package cliClient
 import (
 	"bytes"
 	"encoding/json"
+	"errors"
+	"github.com/datreeio/datree/bl/files"
 	"io/ioutil"
 	"net/http"
 	"path/filepath"
@@ -96,6 +98,31 @@ type GetVersionMessageTestCase struct {
 	}
 }
 
+type PublishPoliciesTestCase struct {
+	name string
+	args struct {
+		policiesConfiguration files.UnknownStruct
+		cliId                 string
+	}
+	mockResponse struct {
+		status int
+		body   struct {
+			message string
+		}
+		error error
+	}
+
+	expected struct {
+		request struct {
+			method  string
+			uri     string
+			body    files.UnknownStruct
+			headers map[string]string
+		}
+		response error
+	}
+}
+
 func TestRequestEvaluation(t *testing.T) {
 	tests := []*RequestEvaluationTestCase{
 		test_requestEvaluation_success(),
@@ -174,6 +201,31 @@ func TestGetVersionMessage(t *testing.T) {
 	}
 }
 
+func TestPublishPolicies(t *testing.T) {
+	tests := []*PublishPoliciesTestCase{
+		test_publishPolicies_success(),
+		test_publishPolicies_schemaError(),
+	}
+	httpClientMock := mockHTTPClient{}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			body, _ := json.Marshal(tt.mockResponse.body)
+			mockedHTTPResponse := httpClient.Response{StatusCode: tt.mockResponse.status, Body: body}
+			httpClientMock.On("Request", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(mockedHTTPResponse, tt.mockResponse.error).Once()
+
+			client := &CliClient{
+				baseUrl:    "http://cli-service.test.io",
+				httpClient: &httpClientMock,
+			}
+
+			actualResponse := client.PublishPolicies(tt.args.policiesConfiguration, tt.args.cliId)
+			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, actualResponse)
+		})
+	}
+}
+
 func readMock(path string) ([]extractor.Configuration, error) {
 	var configurations []extractor.Configuration
 
@@ -384,3 +436,105 @@ func test_createEvaluation_success() *CreateEvaluationTestCase {
 		},
 	}
 }
+
+func test_publishPolicies_success() *PublishPoliciesTestCase {
+	requestPoliciesConfigurationArg := files.UnknownStruct{}
+	return &PublishPoliciesTestCase{
+		name: "success - publish policies",
+		args: struct {
+			policiesConfiguration files.UnknownStruct
+			cliId                 string
+		}{
+			policiesConfiguration: requestPoliciesConfigurationArg,
+			cliId:                 "cli_id",
+		},
+		mockResponse: struct {
+			status int
+			body   struct {
+				message string
+			}
+			error error
+		}{
+			status: http.StatusCreated,
+			body: struct {
+				message string
+			}{
+				message: "",
+			},
+			error: nil,
+		},
+		expected: struct {
+			request struct {
+				method  string
+				uri     string
+				body    files.UnknownStruct
+				headers map[string]string
+			}
+			response error
+		}{
+			request: struct {
+				method  string
+				uri     string
+				body    files.UnknownStruct
+				headers map[string]string
+			}{
+				method:  http.MethodPut,
+				uri:     "/cli/policy/publish/cliIds/cli_id",
+				body:    requestPoliciesConfigurationArg,
+				headers: nil,
+			},
+			response: nil,
+		},
+	}
+}
+
+func test_publishPolicies_schemaError() *PublishPoliciesTestCase {
+	requestPoliciesConfigurationArg := files.UnknownStruct{}
+	return &PublishPoliciesTestCase{
+		name: "schema error - publish policies",
+		args: struct {
+			policiesConfiguration files.UnknownStruct
+			cliId                 string
+		}{
+			policiesConfiguration: requestPoliciesConfigurationArg,
+			cliId:                 "cli_id",
+		},
+		mockResponse: struct {
+			status int
+			body   struct {
+				message string
+			}
+			error error
+		}{
+			status: http.StatusBadRequest,
+			body: struct {
+				message string
+			}{
+				message: "",
+			},
+			error: errors.New("error from cli-service"),
+		},
+		expected: struct {
+			request struct {
+				method  string
+				uri     string
+				body    files.UnknownStruct
+				headers map[string]string
+			}
+			response error
+		}{
+			request: struct {
+				method  string
+				uri     string
+				body    files.UnknownStruct
+				headers map[string]string
+			}{
+				method:  http.MethodPut,
+				uri:     "/cli/policy/publish/cliIds/cli_id",
+				body:    requestPoliciesConfigurationArg,
+				headers: nil,
+			},
+			response: errors.New("error from cli-service"),
+		},
+	}
+}