Repository: datreeio/datree
Pull request #143 (2021-08-22 08:38:54)

feat: Dat 3375 publish command


Patch:
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/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/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/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..eb437a5 100644
--- a/internal/release/deploy.sh
+++ b/internal/release/deploy.sh
@@ -1,10 +1,16 @@
 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
+git add .goreleaser.yml
+git commit -m "ci: Goreleaser brew update tap name"
+
 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/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"),
+		},
+	}
+}