Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(kafka acl): add base and list command #1173

Merged
merged 4 commits into from
Oct 4, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/commands/rhoas_kafka.adoc

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 48 additions & 0 deletions docs/commands/rhoas_kafka_acl.adoc

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

52 changes: 52 additions & 0 deletions docs/commands/rhoas_kafka_acl_list.adoc

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions docs/commands/rhoas_kafka_consumer-group_reset-offset.adoc

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions pkg/cmd/kafka/acl/acl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package acl

import (
"github.com/redhat-developer/app-services-cli/pkg/cmd/factory"
"github.com/redhat-developer/app-services-cli/pkg/cmd/kafka/acl/list"
"github.com/spf13/cobra"
)

// NewAclCommand creates a new command sub-group for Kafka ACL operations
func NewAclCommand(f *factory.Factory) *cobra.Command {
cmd := &cobra.Command{
Use: "acl",
Short: f.Localizer.MustLocalize("kafka.acl.cmd.shortDescription"),
Long: f.Localizer.MustLocalize("kafka.acl.cmd.longDescription"),
Example: f.Localizer.MustLocalize("kafka.acl.cmd.example"),
Args: cobra.ExactArgs(1),
}

cmd.AddCommand(
list.NewListACLCommand(f),
)

return cmd
}
172 changes: 172 additions & 0 deletions pkg/cmd/kafka/acl/list/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package list

import (
"context"
"fmt"
"net/http"
"strings"

"github.com/redhat-developer/app-services-cli/internal/build"
"github.com/redhat-developer/app-services-cli/internal/config"
"github.com/redhat-developer/app-services-cli/pkg/cmd/factory"
"github.com/redhat-developer/app-services-cli/pkg/cmdutil"
"github.com/redhat-developer/app-services-cli/pkg/connection"
"github.com/redhat-developer/app-services-cli/pkg/dump"
"github.com/redhat-developer/app-services-cli/pkg/iostreams"
"github.com/redhat-developer/app-services-cli/pkg/localize"
"github.com/redhat-developer/app-services-cli/pkg/logging"
"github.com/spf13/cobra"

kafkainstanceclient "github.com/redhat-developer/app-services-sdk-go/kafkainstance/apiv1internal/client"
)

type options struct {
Config config.IConfig
Connection factory.ConnectionFunc
Logger logging.Logger
IO *iostreams.IOStreams
localizer localize.Localizer
Context context.Context

page int32
size int32
kafkaID string
output string
}

type permissionsRow struct {
Principal string `json:"principal,omitempty" header:"Principal"`
Permission string `json:"permission,omitempty" header:"permission"`
Description string `json:"description,omitempty" header:"description"`
}

// NewListACLCommand creates a new command to list Kafka ACL rules
func NewListACLCommand(f *factory.Factory) *cobra.Command {

opts := &options{
Config: f.Config,
Connection: f.Connection,
Logger: f.Logger,
IO: f.IOStreams,
localizer: f.Localizer,
Context: f.Context,
}

cmd := &cobra.Command{
Use: "list",
Short: f.Localizer.MustLocalize("kafka.acl.list.cmd.shortDescription"),
Long: f.Localizer.MustLocalize("kafka.acl.list.cmd.longDescription"),
Example: f.Localizer.MustLocalize("kafka.acl.list.cmd.example"),
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {

cfg, err := opts.Config.Load()
if err != nil {
return err
}

if !cfg.HasKafka() {
return opts.localizer.MustLocalizeError("kafka.acl.common.error.noKafkaSelected")
}

opts.kafkaID = cfg.Services.Kafka.ClusterID

return runList(opts)
},
}

cmd.Flags().Int32VarP(&opts.page, "page", "", cmdutil.ConvertPageValueToInt32(build.DefaultPageNumber), opts.localizer.MustLocalize("kafka.acl.list.flag.page.description"))
cmd.Flags().Int32VarP(&opts.size, "size", "", cmdutil.ConvertSizeValueToInt32(build.DefaultPageSize), opts.localizer.MustLocalize("kafka.acl.list.flag.size.description"))
cmd.Flags().StringVarP(&opts.output, "output", "o", "", opts.localizer.MustLocalize("kafka.acl.list.flag.output.description"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
cmd.Flags().StringVarP(&opts.output, "output", "o", "", opts.localizer.MustLocalize("kafka.acl.list.flag.output.description"))
cmd.Flags().StringVarP(&opts.output, "output", "o", dump.EmptyFormat, opts.localizer.MustLocalize("kafka.acl.list.flag.output.description"))


return cmd
}

func runList(opts *options) (err error) {
conn, err := opts.Connection(connection.DefaultConfigRequireMasAuth)
if err != nil {
return err
}

api, kafkaInstance, err := conn.API().KafkaAdmin(opts.kafkaID)
if err != nil {
return err
}

req := api.AclsApi.GetAcls(opts.Context)

req.Page(float32(opts.page))

permissionsData, httpRes, err := req.Execute()
if httpRes != nil {
defer httpRes.Body.Close()
}

if err != nil {
if httpRes == nil {
return err
}

operationTmplPair := localize.NewEntry("Operation", "list")

switch httpRes.StatusCode {
case http.StatusUnauthorized:
return opts.localizer.MustLocalizeError("kafka.acl.common.error.unauthorized", operationTmplPair)
case http.StatusForbidden:
return opts.localizer.MustLocalizeError("kafka.acl.common.error.forbidden", operationTmplPair)
case http.StatusInternalServerError:
return opts.localizer.MustLocalizeError("kafka.acl.common.error.internalServerError")
case http.StatusServiceUnavailable:
return opts.localizer.MustLocalizeError("kafka.acl.common.error.unableToConnectToKafka", localize.NewEntry("Name", kafkaInstance.GetName()))
default:
return err
}
}

switch opts.output {
case dump.EmptyFormat:
opts.Logger.Info("")
permissions := permissionsData.GetItems()
rows := mapPermissionListResultsToTableFormat(permissions, opts.localizer)
dump.Table(opts.IO.Out, rows)
default:
return dump.Formatted(opts.IO.Out, opts.output, permissionsData)
}

return nil
}

func mapPermissionListResultsToTableFormat(permissions []kafkainstanceclient.AclBinding, localizer localize.Localizer) []permissionsRow {

rows := make([]permissionsRow, len(permissions))

for i, p := range permissions {

description := buildDescription(p.PatternType, localizer)
row := permissionsRow{
Principal: formatPrincipal(p.GetPrincipal(), localizer),
Permission: fmt.Sprintf("%s | %s", p.GetPermission(), p.GetOperation()),
Description: fmt.Sprintf("%s %s \"%s\"", p.GetResourceType(), description, p.GetResourceName()),
Comment on lines +150 to +151
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if we should map raw ACL values to something more human readable. Example GROUP => Consumer Group, CLUSTER => Kafka instance.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would keep them as in spec and aligned with the way our documentation will cover them

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah makes sense.

}
rows[i] = row
}
return rows
}

func formatPrincipal(principal string, localizer localize.Localizer) string {
s := strings.Split(principal, ":")[1]

if s == "*" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if s == "*" {
if s == acl.Wildcard {

Something like this will be useful.

return localizer.MustLocalize("kafka.acl.list.allAccounts")
}

return s
}

func buildDescription(patternType kafkainstanceclient.AclPatternType, localizer localize.Localizer) string {
if patternType == kafkainstanceclient.ACLPATTERNTYPE_LITERAL {
return localizer.MustLocalize("kafka.acl.list.is")
}

return localizer.MustLocalize("kafka.acl.list.startsWith")
}
2 changes: 2 additions & 0 deletions pkg/cmd/kafka/kafka.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/spf13/cobra"

"github.com/redhat-developer/app-services-cli/pkg/cmd/factory"
"github.com/redhat-developer/app-services-cli/pkg/cmd/kafka/acl"
"github.com/redhat-developer/app-services-cli/pkg/cmd/kafka/create"
"github.com/redhat-developer/app-services-cli/pkg/cmd/kafka/delete"
"github.com/redhat-developer/app-services-cli/pkg/cmd/kafka/describe"
Expand Down Expand Up @@ -35,6 +36,7 @@ func NewKafkaCommand(f *factory.Factory) *cobra.Command {
topic.NewTopicCommand(f),
consumergroup.NewConsumerGroupCommand(f),
update.NewUpdateCommand(f),
acl.NewAclCommand(f),
)

return cmd
Expand Down
Loading