diff --git a/go.mod b/go.mod index 9fd997e..1427d25 100644 --- a/go.mod +++ b/go.mod @@ -9,10 +9,11 @@ require ( github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de github.com/manifoldco/promptui v0.9.0 github.com/riferrei/srclient v0.5.4 - github.com/sirupsen/logrus v1.8.1 - github.com/spf13/cobra v1.4.0 + github.com/sirupsen/logrus v1.9.0 + github.com/spf13/cobra v1.5.0 + github.com/thediveo/enumflag/v2 v2.0.1 github.com/xdg/scram v1.0.5 - gopkg.in/yaml.v3 v3.0.0-20220512140231-539c8e751b99 + gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -42,9 +43,10 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/xdg/stringprep v1.0.3 // indirect golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect - golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect + golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect + golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect - golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect + golang.org/x/sys v0.2.0 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect ) diff --git a/go.sum b/go.sum index e19e198..a3c12bb 100644 --- a/go.sum +++ b/go.sum @@ -12,7 +12,7 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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= @@ -30,8 +30,8 @@ github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUork github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= @@ -77,6 +77,8 @@ github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHX github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY= +github.com/onsi/gomega v1.20.0 h1:8W0cWlwFkflGPLltQvLRB7ZVD5HuP6ng320w2IS245Q= github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= @@ -95,10 +97,11 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 h1:TToq11gyfNlrMFZiYujSekIsPd9AmsA2Bj/iv+s4JHE= github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= -github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= +github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -108,6 +111,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/thediveo/enumflag/v2 v2.0.1 h1:2bmWZPD2uSARDsOjXIdLRlNcYBFNF9xX0RNUNF2vKic= +github.com/thediveo/enumflag/v2 v2.0.1/go.mod h1:SyxyCNvv0QeRtZ7fjuaUz4FRLC3cWuDiD7QdORU0MGg= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= @@ -120,11 +125,14 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= +golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -137,8 +145,10 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= @@ -149,7 +159,6 @@ golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -162,5 +171,5 @@ gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20220512140231-539c8e751b99 h1:dbuHpmKjkDzSOMKAWl10QNlgaZUd3V1q99xc81tt2Kc= -gopkg.in/yaml.v3 v3.0.0-20220512140231-539c8e751b99/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/pkg/kafka/acl.go b/internal/pkg/kafka/acl.go new file mode 100644 index 0000000..4f12a2e --- /dev/null +++ b/internal/pkg/kafka/acl.go @@ -0,0 +1,71 @@ +package kafka + +import ( + "fmt" + "os" + + "github.com/Shopify/sarama" + "github.com/electric-saw/kafta/pkg/cmd/util" +) + +func ListAllAcls(conn *KafkaConnection) []sarama.ResourceAcls { + filter := sarama.AclFilter{ + ResourcePatternTypeFilter: sarama.AclPatternAny, + ResourceType: sarama.AclResourceAny, + PermissionType: sarama.AclPermissionAny, + Operation: sarama.AclOperationAny, + } + acls, err := conn.Admin.ListAcls(filter) + + util.CheckErr(err) + + return acls +} + +func CreateAcl(conn *KafkaConnection, resource_name string, resource_type sarama.AclResourceType, principal string, host string, operation sarama.AclOperation, permission_type sarama.AclPermissionType) error { + if resource_name == "" { + fmt.Println("Resource name is required") + os.Exit(0) + } + + resource := sarama.Resource{ + ResourceName: resource_name, + ResourceType: resource_type, + } + + acl := sarama.Acl{ + Principal: principal, + Host: host, + Operation: operation, + PermissionType: permission_type, + } + + if err := conn.Admin.CreateACL(resource, acl); err == nil { + fmt.Println("Acl created") + return err + } else { + return err + } +} + +func DeleteAcl(conn *KafkaConnection, resource_name string, resource_type sarama.AclResourceType, principal string, host string, operation sarama.AclOperation, permission_type sarama.AclPermissionType) error { + if resource_name == "" { + fmt.Println("Resource name is required") + os.Exit(0) + } + + if _, err := conn.Admin.DeleteACL(sarama.AclFilter{ + ResourceName: &resource_name, + ResourceType: resource_type, + Principal: &principal, + Host: &host, + Operation: operation, + PermissionType: permission_type, + ResourcePatternTypeFilter: sarama.AclPatternAny, + }, false); err == nil { + fmt.Println("Acl deleted") + return err + } else { + return err + } +} diff --git a/pkg/cmd/cli/acl/acl_cmd.go b/pkg/cmd/cli/acl/acl_cmd.go new file mode 100644 index 0000000..ee877c1 --- /dev/null +++ b/pkg/cmd/cli/acl/acl_cmd.go @@ -0,0 +1,57 @@ +package acl + +import ( + "github.com/Shopify/sarama" + "github.com/electric-saw/kafta/internal/pkg/configuration" + "github.com/spf13/cobra" +) + +func NewCmdAcl(config *configuration.Configuration) *cobra.Command { + cmd := &cobra.Command{ + Use: "acl", + Short: "Acls management", + } + + cmd.AddCommand(NewCmdListAcl(config)) + cmd.AddCommand(NewCmdCreateAcl(config)) + cmd.AddCommand(NewCmdDeleteAcl(config)) + + return cmd +} + +type AclOptions struct { + resource_name string + resource_type sarama.AclResourceType + acl_principal string + acl_host string + acl_operation sarama.AclOperation + acl_permission_type sarama.AclPermissionType +} + +var ResourceTypeMapping = map[sarama.AclResourceType][]string{ + sarama.AclResourceAny: {"Any"}, + sarama.AclResourceTopic: {"Topic"}, + sarama.AclResourceGroup: {"Group"}, + sarama.AclResourceCluster: {"Cluster"}, + sarama.AclResourceTransactionalID: {"TransactionalID"}, + sarama.AclResourceDelegationToken: {"DelegationToken"}, +} + +var OperationMapping = map[sarama.AclOperation][]string{ + sarama.AclOperationAll: {"All"}, + sarama.AclOperationRead: {"Read"}, + sarama.AclOperationWrite: {"Write"}, + sarama.AclOperationCreate: {"Create"}, + sarama.AclOperationDelete: {"Delete"}, + sarama.AclOperationAlter: {"Alter"}, + sarama.AclOperationDescribe: {"Describe"}, + sarama.AclOperationClusterAction: {"ClusterAction"}, + sarama.AclOperationDescribeConfigs: {"DescribeConfigs"}, + sarama.AclOperationAlterConfigs: {"AlterConfigs"}, + sarama.AclOperationIdempotentWrite: {"IdempotentWrite"}, +} + +var PermissionMapping = map[sarama.AclPermissionType][]string{ + sarama.AclPermissionDeny: {"Deny"}, + sarama.AclPermissionAllow: {"Allow"}, +} diff --git a/pkg/cmd/cli/acl/create_acl.go b/pkg/cmd/cli/acl/create_acl.go new file mode 100644 index 0000000..9006260 --- /dev/null +++ b/pkg/cmd/cli/acl/create_acl.go @@ -0,0 +1,76 @@ +package acl + +import ( + "github.com/Shopify/sarama" + "github.com/electric-saw/kafta/internal/pkg/configuration" + "github.com/electric-saw/kafta/internal/pkg/kafka" + cmdutil "github.com/electric-saw/kafta/pkg/cmd/util" + "github.com/spf13/cobra" + "github.com/thediveo/enumflag/v2" +) + +type createAclOptions struct { + config *configuration.Configuration + AclOptions +} + +func NewCmdCreateAcl(config *configuration.Configuration) *cobra.Command { + options := &createAclOptions{config: config} + cmd := &cobra.Command{ + Use: "create [NAME] [--type=Topic] [--principal=User:CN=principal] [--host=*] [--operation=All] [--permission=Allow]", + Short: "Create acl", + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(options.complete(cmd)) + cmdutil.CheckErr(options.run()) + }, + } + + cmd.Flags().StringVarP(&options.resource_name, "configs", "c", "*", "Configs") + + cmd.Flags().VarP( + enumflag.New(&options.resource_type, "type", ResourceTypeMapping, enumflag.EnumCaseInsensitive), + "type", "t", + "resource type can be 'Topic', 'Group', 'Cluster', 'TransactionalID', 'DelegationToken'") + + cmd.Flags().StringVarP(&options.acl_principal, "principal", "p", "", "Principal") + + cmd.Flags().StringVarP(&options.acl_host, "host", "s", "*", "Host") + + cmd.Flags().VarP( + enumflag.New(&options.acl_operation, "operation", OperationMapping, enumflag.EnumCaseInsensitive), + "operation", "o", + "acl operation can be 'All', 'Read', 'Write', 'Create', 'Delete', 'Alter', 'Describe', 'ClusterAction', 'DescribeConfigs', 'AlterConfigs', 'IdempotentWrite'") + + cmd.Flags().VarP( + enumflag.NewWithoutDefault(&options.acl_permission_type, "permission", PermissionMapping, enumflag.EnumCaseInsensitive), + "permission", "m", + "acl permission can be 'Deny', 'Allow'") + + return cmd +} + +func (o *createAclOptions) complete(cmd *cobra.Command) error { + args := cmd.Flags().Args() + if len(args) > 1 { + return cmdutil.HelpErrorf(cmd, "Unexpected args: %v", args) + } + if len(args) == 1 { + o.resource_name = args[0] + } + if o.resource_type == sarama.AclResourceUnknown { + o.resource_type = sarama.AclResourceTopic + } + if o.acl_operation == sarama.AclOperationUnknown { + o.acl_operation = sarama.AclOperationAll + } + if o.acl_permission_type == sarama.AclPermissionUnknown { + o.acl_permission_type = sarama.AclPermissionAllow + } + return nil +} + +func (o *createAclOptions) run() error { + conn := kafka.MakeConnection(o.config) + defer conn.Close() + return kafka.CreateAcl(conn, o.resource_name, o.resource_type, o.acl_principal, o.acl_host, o.acl_operation, o.acl_permission_type) +} diff --git a/pkg/cmd/cli/acl/delete_acl.go b/pkg/cmd/cli/acl/delete_acl.go new file mode 100644 index 0000000..83a1495 --- /dev/null +++ b/pkg/cmd/cli/acl/delete_acl.go @@ -0,0 +1,76 @@ +package acl + +import ( + "github.com/Shopify/sarama" + "github.com/electric-saw/kafta/internal/pkg/configuration" + "github.com/electric-saw/kafta/internal/pkg/kafka" + cmdutil "github.com/electric-saw/kafta/pkg/cmd/util" + "github.com/spf13/cobra" + "github.com/thediveo/enumflag/v2" +) + +type deleteAclOptions struct { + config *configuration.Configuration + AclOptions +} + +func NewCmdDeleteAcl(config *configuration.Configuration) *cobra.Command { + options := &deleteAclOptions{config: config} + cmd := &cobra.Command{ + Use: "delete [NAME] [--type=Topic] [--principal=User:CN=principal] [--host=*] [--operation=All] [--permission=Allow]", + Short: "delete acls", + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(options.complete(cmd)) + cmdutil.CheckErr(options.run()) + }, + } + + cmd.Flags().StringVarP(&options.resource_name, "configs", "c", "*", "Configs") + + cmd.Flags().VarP( + enumflag.New(&options.resource_type, "type", ResourceTypeMapping, enumflag.EnumCaseInsensitive), + "type", "t", + "resource type can be 'Any', 'Topic', 'Group', 'Cluster', 'TransactionalID', 'DelegationToken'") + + cmd.Flags().StringVarP(&options.acl_principal, "principal", "p", "", "Principal") + + cmd.Flags().StringVarP(&options.acl_host, "host", "s", "*", "Host") + + cmd.Flags().VarP( + enumflag.New(&options.acl_operation, "operation", OperationMapping, enumflag.EnumCaseInsensitive), + "operation", "o", + "acl operation can be 'Any', 'All', 'Read', 'Write', 'Create', 'Delete', 'Alter', 'Describe', 'ClusterAction', 'DescribeConfigs', 'AlterConfigs', 'IdempotentWrite'") + + cmd.Flags().VarP( + enumflag.NewWithoutDefault(&options.acl_permission_type, "permission", PermissionMapping, enumflag.EnumCaseInsensitive), + "permission", "m", + "acl permission can be 'Any', 'Deny', 'Allow'") + + return cmd +} + +func (o *deleteAclOptions) complete(cmd *cobra.Command) error { + args := cmd.Flags().Args() + if len(args) > 1 { + return cmdutil.HelpErrorf(cmd, "Unexpected args: %v", args) + } + if len(args) == 1 { + o.resource_name = args[0] + } + if o.resource_type == sarama.AclResourceUnknown { + o.resource_type = sarama.AclResourceAny + } + if o.acl_operation == sarama.AclOperationUnknown { + o.acl_operation = sarama.AclOperationAny + } + if o.acl_permission_type == sarama.AclPermissionUnknown { + o.acl_permission_type = sarama.AclPermissionAny + } + return nil +} + +func (o *deleteAclOptions) run() error { + conn := kafka.MakeConnection(o.config) + defer conn.Close() + return kafka.DeleteAcl(conn, o.resource_name, o.resource_type, o.acl_principal, o.acl_host, o.acl_operation, o.acl_permission_type) +} diff --git a/pkg/cmd/cli/acl/list_acls.go b/pkg/cmd/cli/acl/list_acls.go new file mode 100644 index 0000000..feb4792 --- /dev/null +++ b/pkg/cmd/cli/acl/list_acls.go @@ -0,0 +1,60 @@ +package acl + +import ( + "fmt" + + "github.com/Shopify/sarama" + "github.com/electric-saw/kafta/internal/pkg/configuration" + "github.com/electric-saw/kafta/internal/pkg/kafka" + cmdutil "github.com/electric-saw/kafta/pkg/cmd/util" + "github.com/jedib0t/go-pretty/v6/table" + "github.com/spf13/cobra" +) + +type listAclOptions struct { + config *configuration.Configuration +} + +func NewCmdListAcl(config *configuration.Configuration) *cobra.Command { + options := &listAclOptions{config: config} + cmd := &cobra.Command{ + Use: "list", + Short: "List acls", + Run: func(cmd *cobra.Command, args []string) { + options.run() + }, + } + + return cmd +} + +func (o *listAclOptions) run() { + conn := kafka.MakeConnection(o.config) + defer conn.Close() + acls := kafka.ListAllAcls(conn) + + for _, acl := range acls { + o.printResourceInfo(acl) + o.printAcls(acl.Acls) + } +} + +func (o *listAclOptions) printResourceInfo(resourceAcl sarama.ResourceAcls) { + header := table.Row{"resource type", "resource name", "resource pattern", "acl count"} + rows := []table.Row{} + rows = append(rows, table.Row{resourceAcl.Resource.ResourceType.String(), resourceAcl.Resource.ResourceName, resourceAcl.Resource.ResourcePatternType.String(), len(resourceAcl.Acls)}) + cmdutil.PrintTable(header, rows) +} + +func (o *listAclOptions) printAcls(acls []*sarama.Acl) { + tab := table.NewWriter() + tab.SetStyle(table.StyleDefault) + tab.AppendHeader(table.Row{"principal", "host", "operation", "permission type"}) + + for _, acl := range acls { + tab.AppendRow(table.Row{acl.Principal, acl.Host, acl.Operation.String(), acl.PermissionType.String()}) + } + + tab.SetStyle(table.StyleDefault) + fmt.Println(tab.Render()) +} diff --git a/pkg/cmd/cli/topic/create_topic.go b/pkg/cmd/cli/topic/create_topic.go index 4300c62..38e64e8 100644 --- a/pkg/cmd/cli/topic/create_topic.go +++ b/pkg/cmd/cli/topic/create_topic.go @@ -1,8 +1,6 @@ package topic import ( - "strings" - "github.com/electric-saw/kafta/internal/pkg/configuration" "github.com/electric-saw/kafta/internal/pkg/kafka" cmdutil "github.com/electric-saw/kafta/pkg/cmd/util" @@ -50,17 +48,5 @@ func (o *createTopicOptions) complete(cmd *cobra.Command) error { func (o *createTopicOptions) run() error { conn := kafka.MakeConnection(o.config) defer conn.Close() - return kafka.CreateTopic(conn, o.name, o.partitions, o.rf, stringToMapPointer(o.topicConfigs)) -} - -// mapToMapPointer split string=string to a map[string]string -func stringToMapPointer(s string) map[string]*string { - m := make(map[string]*string) - for _, v := range strings.Split(s, ",") { - kv := strings.Split(v, "=") - if len(kv) == 2 { - m[kv[0]] = &kv[1] - } - } - return m + return kafka.CreateTopic(conn, o.name, o.partitions, o.rf, cmdutil.StringToMapPointer(o.topicConfigs)) } diff --git a/pkg/cmd/kafta/kafta.go b/pkg/cmd/kafta/kafta.go index 265415f..4e3618c 100644 --- a/pkg/cmd/kafta/kafta.go +++ b/pkg/cmd/kafta/kafta.go @@ -2,6 +2,7 @@ package kafta import ( "github.com/electric-saw/kafta/internal/pkg/configuration" + "github.com/electric-saw/kafta/pkg/cmd/cli/acl" "github.com/electric-saw/kafta/pkg/cmd/cli/broker" "github.com/electric-saw/kafta/pkg/cmd/cli/cluster" "github.com/electric-saw/kafta/pkg/cmd/cli/completion" @@ -41,6 +42,7 @@ func NewKaftaCommand(name string) *cobra.Command { root.AddCommand(cluster.NewCmdCluster(config)) root.AddCommand(schema.NewCmdSchema(config)) root.AddCommand(console.NewCmdConsole(config)) + root.AddCommand(acl.NewCmdAcl(config)) return root } diff --git a/pkg/cmd/util/helpers.go b/pkg/cmd/util/helpers.go index bc619f8..24d75cb 100644 --- a/pkg/cmd/util/helpers.go +++ b/pkg/cmd/util/helpers.go @@ -59,3 +59,15 @@ func HelpError(cmd *cobra.Command, args ...interface{}) error { msg := fmt.Sprint(args...) return fmt.Errorf("%s", msg) } + +// mapToMapPointer split string=string to a map[string]string +func StringToMapPointer(s string) map[string]*string { + m := make(map[string]*string) + for _, v := range strings.Split(s, ",") { + kv := strings.Split(v, "=") + if len(kv) == 2 { + m[kv[0]] = &kv[1] + } + } + return m +}