Skip to content

Commit f92c4c3

Browse files
authored
feat(controlplane): organization invite system (#404)
Signed-off-by: Miguel Martinez Trivino <[email protected]>
1 parent 041cd2d commit f92c4c3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+7305
-32
lines changed

app/cli/cmd/organization.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ func newOrganizationCmd() *cobra.Command {
2626
Short: "Organizations management",
2727
}
2828

29-
cmd.AddCommand(newOrganizationList(), newOrganizationSet(), newOrganizationDescribeCmd())
29+
cmd.AddCommand(
30+
newOrganizationList(),
31+
newOrganizationSet(),
32+
newOrganizationDescribeCmd(),
33+
newOrganizationInvitationCmd(),
34+
)
3035
return cmd
3136
}

app/cli/cmd/organization_describe.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func contextTableOutput(config *action.ConfigContextItem) error {
4646
gt.SetTitle("Current Context")
4747
gt.AppendRow(table.Row{"Logged in as", config.CurrentUser.Email})
4848
gt.AppendSeparator()
49-
gt.AppendRow(table.Row{"Organization", config.CurrentOrg.Name})
49+
gt.AppendRow(table.Row{"Organization", fmt.Sprintf("%s (%s)", config.CurrentOrg.Name, config.CurrentOrg.ID)})
5050
backend := config.CurrentCASBackend
5151
if backend != nil {
5252
gt.AppendSeparator()
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//
2+
// Copyright 2023 The Chainloop Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package cmd
17+
18+
import (
19+
"github.com/spf13/cobra"
20+
)
21+
22+
func newOrganizationInvitationCmd() *cobra.Command {
23+
cmd := &cobra.Command{
24+
Use: "invitation",
25+
Aliases: []string{"invite"},
26+
Short: "User Invitations",
27+
}
28+
29+
cmd.AddCommand(newOrganizationInvitationListSentCmd(), newOrganizationInvitationCreateCmd(), newOrganizationInvitationRevokeCmd())
30+
31+
return cmd
32+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//
2+
// Copyright 2023 The Chainloop Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package cmd
17+
18+
import (
19+
"context"
20+
21+
"github.com/chainloop-dev/chainloop/app/cli/internal/action"
22+
"github.com/spf13/cobra"
23+
)
24+
25+
func newOrganizationInvitationCreateCmd() *cobra.Command {
26+
var receiverEmail, organizationID string
27+
cmd := &cobra.Command{
28+
Use: "create",
29+
Short: "Invite a User to an Organization",
30+
RunE: func(cmd *cobra.Command, args []string) error {
31+
res, err := action.NewOrgInvitationCreate(actionOpts).Run(
32+
context.Background(), organizationID, receiverEmail)
33+
if err != nil {
34+
return err
35+
}
36+
37+
return encodeOutput([]*action.OrgInvitationItem{res}, orgInvitationTableOutput)
38+
},
39+
}
40+
41+
cmd.Flags().StringVar(&receiverEmail, "receiver", "", "Email of the user to invite")
42+
err := cmd.MarkFlagRequired("receiver")
43+
cobra.CheckErr(err)
44+
45+
cmd.Flags().StringVar(&organizationID, "organization", "", "ID of the organization to invite the user to")
46+
err = cmd.MarkFlagRequired("organization")
47+
cobra.CheckErr(err)
48+
49+
return cmd
50+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//
2+
// Copyright 2023 The Chainloop Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package cmd
17+
18+
import (
19+
"context"
20+
"fmt"
21+
"time"
22+
23+
"github.com/chainloop-dev/chainloop/app/cli/internal/action"
24+
"github.com/jedib0t/go-pretty/v6/table"
25+
"github.com/spf13/cobra"
26+
)
27+
28+
func newOrganizationInvitationListSentCmd() *cobra.Command {
29+
cmd := &cobra.Command{
30+
Use: "list",
31+
Aliases: []string{"ls"},
32+
Short: "List sent invitations",
33+
RunE: func(cmd *cobra.Command, args []string) error {
34+
res, err := action.NewOrgInvitationListSent(actionOpts).Run(context.Background())
35+
if err != nil {
36+
return err
37+
}
38+
39+
return encodeOutput(res, orgInvitationTableOutput)
40+
},
41+
}
42+
43+
return cmd
44+
}
45+
46+
func orgInvitationTableOutput(items []*action.OrgInvitationItem) error {
47+
if len(items) == 0 {
48+
fmt.Println("there are no sent invitations")
49+
return nil
50+
}
51+
52+
t := newTableWriter()
53+
t.AppendHeader(table.Row{"ID", "Org Name", "Receiver Email", "Status", "Created At"})
54+
55+
for _, i := range items {
56+
t.AppendRow(table.Row{i.ID, i.Organization.Name, i.ReceiverEmail, i.Status, i.CreatedAt.Format(time.RFC822)})
57+
t.AppendSeparator()
58+
}
59+
60+
t.Render()
61+
return nil
62+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//
2+
// Copyright 2023 The Chainloop Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package cmd
17+
18+
import (
19+
"context"
20+
21+
"github.com/chainloop-dev/chainloop/app/cli/internal/action"
22+
"github.com/spf13/cobra"
23+
)
24+
25+
func newOrganizationInvitationRevokeCmd() *cobra.Command {
26+
var invitationID string
27+
cmd := &cobra.Command{
28+
Use: "revoke",
29+
Short: "Revoke a pending invitation",
30+
RunE: func(cmd *cobra.Command, args []string) error {
31+
if err := action.NewOrgInvitationRevoke(actionOpts).Run(context.Background(), invitationID); err != nil {
32+
return err
33+
}
34+
35+
logger.Info().Msg("Invitation Revoked!")
36+
return nil
37+
},
38+
}
39+
40+
cmd.Flags().StringVar(&invitationID, "id", "", "Invitation ID")
41+
err := cmd.MarkFlagRequired("id")
42+
cobra.CheckErr(err)
43+
44+
return cmd
45+
}

app/cli/cmd/organization_list.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ func orgMembershipTableOutput(items []*action.MembershipItem) error {
4949
}
5050

5151
t := newTableWriter()
52-
t.AppendHeader(table.Row{"ID", "Org Name", "Current", "Joined At"})
52+
t.AppendHeader(table.Row{"Org ID", "Org Name", "Current", "Joined At"})
5353

5454
for _, i := range items {
55-
t.AppendRow(table.Row{i.ID, i.Org.Name, i.Current, i.CreatedAt.Format(time.RFC822)})
55+
t.AppendRow(table.Row{i.Org.ID, i.Org.Name, i.Current, i.CreatedAt.Format(time.RFC822)})
5656
t.AppendSeparator()
5757
}
5858

app/cli/cmd/organization_set.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,48 @@
1616
package cmd
1717

1818
import (
19+
"fmt"
20+
1921
"github.com/chainloop-dev/chainloop/app/cli/internal/action"
2022
"github.com/spf13/cobra"
2123
)
2224

2325
func newOrganizationSet() *cobra.Command {
24-
var membershipID string
26+
var orgID string
2527

2628
cmd := &cobra.Command{
2729
Use: "set",
2830
Short: "Set the current organization associated with this user",
2931
RunE: func(cmd *cobra.Command, args []string) error {
30-
res, err := action.NewMembershipSet(actionOpts).Run(membershipID)
32+
// To change the current organization, we need to find the membership ID
33+
memberships, err := action.NewMembershipList(actionOpts).Run()
34+
if err != nil {
35+
return err
36+
}
37+
38+
var membershipID string
39+
for _, m := range memberships {
40+
if m.Org.ID == orgID {
41+
membershipID = m.ID
42+
break
43+
}
44+
}
45+
46+
if membershipID == "" {
47+
return fmt.Errorf("organization %s not found", orgID)
48+
}
49+
50+
m, err := action.NewMembershipSet(actionOpts).Run(membershipID)
3151
if err != nil {
3252
return err
3353
}
3454

3555
logger.Info().Msg("Organization switched!")
36-
return encodeOutput([]*action.MembershipItem{res}, orgMembershipTableOutput)
56+
return encodeOutput([]*action.MembershipItem{m}, orgMembershipTableOutput)
3757
},
3858
}
3959

40-
cmd.Flags().StringVar(&membershipID, "id", "", "membership ID to make the switch")
60+
cmd.Flags().StringVar(&orgID, "id", "", "organization ID to make the switch")
4161
cobra.CheckErr(cmd.MarkFlagRequired("id"))
4262

4363
return cmd

app/cli/cmd/output.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ type tabulatedData interface {
4343
[]*action.AvailableIntegrationItem |
4444
[]*action.AttachedIntegrationItem |
4545
[]*action.MembershipItem |
46-
[]*action.CASBackendItem
46+
[]*action.CASBackendItem |
47+
[]*action.OrgInvitationItem
4748
}
4849

4950
var ErrOutputFormatNotImplemented = errors.New("format not implemented")

app/cli/internal/action/config_current_context.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ func NewConfigCurrentContext(cfg *ActionsOpts) *ConfigCurrentContext {
3131
}
3232

3333
type ConfigContextItem struct {
34-
CurrentUser *ConfigContextItemUser
34+
CurrentUser *UserItem
3535
CurrentOrg *OrgItem
3636
CurrentCASBackend *CASBackendItem
3737
}
3838

39-
type ConfigContextItemUser struct {
39+
type UserItem struct {
4040
ID, Email string
4141
CreatedAt *time.Time
4242
}
@@ -51,12 +51,20 @@ func (action *ConfigCurrentContext) Run() (*ConfigContextItem, error) {
5151
res := resp.GetResult()
5252

5353
return &ConfigContextItem{
54-
CurrentUser: &ConfigContextItemUser{
55-
ID: res.GetCurrentUser().Id,
56-
Email: res.GetCurrentUser().Email,
57-
CreatedAt: toTimePtr(res.GetCurrentUser().CreatedAt.AsTime()),
58-
},
54+
CurrentUser: pbUserItemToAction(res.GetCurrentUser()),
5955
CurrentOrg: pbOrgItemToAction(res.GetCurrentOrg()),
6056
CurrentCASBackend: pbCASBackendItemToAction(res.GetCurrentCasBackend()),
6157
}, nil
6258
}
59+
60+
func pbUserItemToAction(in *pb.User) *UserItem {
61+
if in == nil {
62+
return nil
63+
}
64+
65+
return &UserItem{
66+
ID: in.Id,
67+
Email: in.Email,
68+
CreatedAt: toTimePtr(in.CreatedAt.AsTime()),
69+
}
70+
}

0 commit comments

Comments
 (0)