From 9b71b214b8dab04fa325404c2ec2deecbc921339 Mon Sep 17 00:00:00 2001 From: Ivan Plantevin Date: Tue, 26 Jul 2022 09:43:17 +0200 Subject: [PATCH] feat(strm-1433): implement invite users command (#98) * feat(strm-1433): implement invite users command * feat(strm-1433): handle issues from response * fix: update api defs version Co-authored-by: Bob van den Hoogen --- go.mod | 2 +- go.sum | 4 +- pkg/bootstrap/bootstrap.go | 3 ++ pkg/cmd/invite.go | 17 ++++++ pkg/common/constants.go | 1 + pkg/entity/organization/cmd.go | 33 ++++++++++++ pkg/entity/organization/organization.go | 71 +++++++++++++++++++++++++ pkg/entity/organization/printers.go | 29 ++++++++++ 8 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 pkg/cmd/invite.go create mode 100644 pkg/entity/organization/cmd.go create mode 100644 pkg/entity/organization/organization.go create mode 100644 pkg/entity/organization/printers.go diff --git a/go.mod b/go.mod index f84d529..a91ae72 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/spf13/cobra v1.3.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.10.0 - github.com/strmprivacy/api-definitions-go/v2 v2.43.1 + github.com/strmprivacy/api-definitions-go/v2 v2.46.0 golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c google.golang.org/grpc v1.46.0 diff --git a/go.sum b/go.sum index 6220c1f..a20c70a 100644 --- a/go.sum +++ b/go.sum @@ -411,8 +411,8 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 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/strmprivacy/api-definitions-go/v2 v2.43.1 h1:saiY1lAElmviKOv8e0IjQGpoitypMFHqglWjp1Za3DU= -github.com/strmprivacy/api-definitions-go/v2 v2.43.1/go.mod h1:3uFjMuBEQSzrRQzaKEgIrLbBWRdya9DTYCQZqyS7nEw= +github.com/strmprivacy/api-definitions-go/v2 v2.46.0 h1:0Sq6rDSVGGlL/6wMZXpCvt/SwAtqpiDqN8pU4jeLycE= +github.com/strmprivacy/api-definitions-go/v2 v2.46.0/go.mod h1:3uFjMuBEQSzrRQzaKEgIrLbBWRdya9DTYCQZqyS7nEw= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= diff --git a/pkg/bootstrap/bootstrap.go b/pkg/bootstrap/bootstrap.go index 9fd2650..b6331a8 100644 --- a/pkg/bootstrap/bootstrap.go +++ b/pkg/bootstrap/bootstrap.go @@ -20,6 +20,7 @@ import ( "strmprivacy/strm/pkg/entity/kafka_exporter" "strmprivacy/strm/pkg/entity/kafka_user" "strmprivacy/strm/pkg/entity/key_stream" + "strmprivacy/strm/pkg/entity/organization" "strmprivacy/strm/pkg/entity/project" "strmprivacy/strm/pkg/entity/schema" "strmprivacy/strm/pkg/entity/schema_code" @@ -47,6 +48,7 @@ func SetupVerbs(rootCmd *cobra.Command) { rootCmd.AddCommand(cmd.ContextCommand) rootCmd.AddCommand(cmd.ActivateCmd) rootCmd.AddCommand(cmd.ArchiveCmd) + rootCmd.AddCommand(cmd.InviteCmd) } func SetupServiceClients(accessToken *string) { @@ -67,6 +69,7 @@ func SetupServiceClients(accessToken *string) { installation.SetupClient(clientConnection, ctx) account.SetupClient(clientConnection, ctx) project.SetupClient(clientConnection, ctx) + organization.SetupClient(clientConnection, ctx) } func ConfigPath() string { diff --git a/pkg/cmd/invite.go b/pkg/cmd/invite.go new file mode 100644 index 0000000..1e58143 --- /dev/null +++ b/pkg/cmd/invite.go @@ -0,0 +1,17 @@ +package cmd + +import ( + "github.com/spf13/cobra" + "strmprivacy/strm/pkg/common" + "strmprivacy/strm/pkg/entity/organization" +) + +var InviteCmd = &cobra.Command{ + Use: common.InviteCommandName, + DisableAutoGenTag: true, + Short: "Invite users to your organization", +} + +func init() { + InviteCmd.AddCommand(organization.InviteUsersCmd()) +} diff --git a/pkg/common/constants.go b/pkg/common/constants.go index dc10357..43e303c 100644 --- a/pkg/common/constants.go +++ b/pkg/common/constants.go @@ -34,6 +34,7 @@ const CreateCommandName = "create" const DeleteCommandName = "delete" const ActivateCommandName = "activate" const ArchiveCommandName = "archive" +const InviteCommandName = "invite" const RecursiveFlagName = "recursive" const RecursiveFlagUsage = "Retrieve entities and their dependents" diff --git a/pkg/entity/organization/cmd.go b/pkg/entity/organization/cmd.go new file mode 100644 index 0000000..eacaf4b --- /dev/null +++ b/pkg/entity/organization/cmd.go @@ -0,0 +1,33 @@ +package organization + +import ( + "github.com/spf13/cobra" +) + +const ( + userEmailsFileFlag = "user-emails-file" +) + +var inviteLongDoc = `Invite one or more users to your organization, by email. + +Either provide the emails comma-separated on the command line, or pass a file +with the -f flag containing one email address per line. + +### Usage` + +func InviteUsersCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "users [first-email,second-email,...]", + Short: "Invite users to your organization by email", + Long: inviteLongDoc, + DisableAutoGenTag: true, + Run: func(cmd *cobra.Command, args []string) { + inviteUsers(args, cmd) + }, + Args: cobra.MaximumNArgs(1), + } + + flags := cmd.Flags() + flags.StringP(userEmailsFileFlag, "f", "", "file with users to invite, one email per line") + return cmd +} diff --git a/pkg/entity/organization/organization.go b/pkg/entity/organization/organization.go new file mode 100644 index 0000000..1fdb615 --- /dev/null +++ b/pkg/entity/organization/organization.go @@ -0,0 +1,71 @@ +package organization + +import ( + "context" + "errors" + "fmt" + "github.com/spf13/cobra" + "github.com/strmprivacy/api-definitions-go/v2/api/organizations/v1" + "google.golang.org/grpc" + "io/ioutil" + "strings" + "strmprivacy/strm/pkg/common" + "strmprivacy/strm/pkg/util" +) + +var client organizations.OrganizationsServiceClient +var apiContext context.Context + +func SetupClient(clientConnection *grpc.ClientConn, ctx context.Context) { + apiContext = ctx + client = organizations.NewOrganizationsServiceClient(clientConnection) +} + +func inviteUsers(args []string, cmd *cobra.Command) { + if apiContext == nil { + common.CliExit(errors.New(fmt.Sprint("No login information found. Use: `dstrm auth login` first."))) + } + emails := getEmails(args, cmd) + var invites []*organizations.UserInvite + for _, email := range emails { + invites = append(invites, &organizations.UserInvite{Email: email}) + } + req := &organizations.InviteUsersRequest{ + UserInvites: invites, + } + response, err := client.InviteUsers(apiContext, req) + common.CliExit(err) + handleInviteResponse(response) +} + +func handleInviteResponse(response *organizations.InviteUsersResponse) { + fmt.Println(fmt.Sprintf("Invited %d users to your organization.", response.InviteCount)) + if len(response.Issues) > 0 { + fmt.Println(fmt.Sprintf("There were %d invites with issues:\n", len(response.Issues))) + inviteIssuesPrinter{}.Print(response.Issues) + } +} + +func getEmails(args []string, cmd *cobra.Command) []string { + emailsFile := util.GetStringAndErr(cmd.Flags(), userEmailsFileFlag) + var emails []string + + if len(args) == 0 && emailsFile == "" { + common.CliExit(errors.New(fmt.Sprint("Either provide comma-separated emails on the command line, or a file containing emails."))) + } else if len(args) > 0 { + emails = strings.Split(args[0], ",") + } else { + emails = read(emailsFile) + } + return emails +} + +func read(emailsFile string) []string { + buf, err := ioutil.ReadFile(emailsFile) + common.CliExit(err) + splitFn := func(c rune) bool { + return c == '\n' + } + // FieldsFunc is used instead of split to filter out any (trailing) empty lines + return strings.FieldsFunc(string(buf), splitFn) +} diff --git a/pkg/entity/organization/printers.go b/pkg/entity/organization/printers.go new file mode 100644 index 0000000..241f3f8 --- /dev/null +++ b/pkg/entity/organization/printers.go @@ -0,0 +1,29 @@ +package organization + +import ( + "github.com/jedib0t/go-pretty/v6/table" + "github.com/strmprivacy/api-definitions-go/v2/api/organizations/v1" + "strmprivacy/strm/pkg/util" +) + +type inviteIssuesPrinter struct{} + +func (p inviteIssuesPrinter) Print(data interface{}) { + issues, _ := (data).([]*organizations.InviteUsersResponse_UserInviteIssue) + rows := make([]table.Row, 0, len(issues)) + + for _, issue := range issues { + row := table.Row{ + issue.Invite.Email, + issue.Message, + } + rows = append(rows, row) + } + + header := table.Row{ + "Email", + "Issue", + } + + util.RenderTable(header, rows) +}