diff --git a/cmd/rig/cmd/completions/completions.go b/cmd/rig/cmd/completions/completions.go index 4af425535..57cb2bfd9 100644 --- a/cmd/rig/cmd/completions/completions.go +++ b/cmd/rig/cmd/completions/completions.go @@ -9,6 +9,7 @@ import ( "connectrpc.com/connect" "github.com/rigdev/rig-go-api/api/v1/capsule" "github.com/rigdev/rig-go-api/api/v1/environment" + "github.com/rigdev/rig-go-api/api/v1/group" "github.com/rigdev/rig-go-api/api/v1/project" "github.com/rigdev/rig-go-sdk" "github.com/rigdev/rig/cmd/rig/cmd/cmdconfig" @@ -41,6 +42,10 @@ func formatEnvironment(e *environment.Environment) string { return fmt.Sprintf("%v\t (Operator Version: %v)", e.GetEnvironmentId(), operatorVersion) } +func formatGroup(g *group.Group) string { + return fmt.Sprintf("%s\t (#Members: %v)", g.GetGroupId(), g.GetNumMembers()) +} + func Contexts( toComplete string, config *cmdconfig.Config, @@ -64,14 +69,9 @@ func Environments( ctx context.Context, rig rig.Client, toComplete string, - scope scope.Scope, ) ([]string, cobra.ShellCompDirective) { var environmentIDs []string - if scope.GetCurrentContext() == nil || scope.GetCurrentContext().GetAuth() == nil { - return nil, cobra.ShellCompDirectiveError - } - resp, err := rig.Environment().List(ctx, &connect.Request[environment.ListRequest]{ Msg: &environment.ListRequest{}, }) @@ -96,14 +96,9 @@ func Projects( ctx context.Context, rig rig.Client, toComplete string, - scope scope.Scope, ) ([]string, cobra.ShellCompDirective) { var projectIDs []string - if scope.GetCurrentContext() == nil || scope.GetCurrentContext().GetAuth() == nil { - return nil, cobra.ShellCompDirectiveError - } - resp, err := rig.Project().List(ctx, &connect.Request[project.ListRequest]{ Msg: &project.ListRequest{}, }) @@ -154,6 +149,27 @@ func Capsules( return capsuleIDs, cobra.ShellCompDirectiveDefault } +func Groups(ctx context.Context, + rig rig.Client, + toComplete string, +) ([]string, cobra.ShellCompDirective) { + completions := []string{} + resp, err := rig.Group().List(ctx, &connect.Request[group.ListRequest]{ + Msg: &group.ListRequest{}, + }) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + for _, g := range resp.Msg.GetGroups() { + if strings.HasPrefix(g.GetGroupId(), toComplete) { + completions = append(completions, formatGroup(g)) + } + } + + return completions, cobra.ShellCompDirectiveNoFileComp +} + func OutputType(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) { diff --git a/cmd/rig/cmd/config/setup.go b/cmd/rig/cmd/config/setup.go index dfd4ba482..74e54b5b1 100644 --- a/cmd/rig/cmd/config/setup.go +++ b/cmd/rig/cmd/config/setup.go @@ -17,6 +17,14 @@ import ( "go.uber.org/fx" ) +var minify bool + +var ( + field string + value string + contextName string +) + type CmdWScope struct { fx.In @@ -26,14 +34,6 @@ type CmdWScope struct { Prompter common.Prompter } -var minify bool - -var ( - field string - value string - contextName string -) - var cmdWScope CmdWScope func initCmdWScope(c CmdWScope) { @@ -216,7 +216,7 @@ func (c *CmdNoScope) completions( s *cli.SetupContext, ) ([]string, cobra.ShellCompDirective) { - if err := s.ExecuteInvokes(cmd, args, initCmdWScope); err != nil { + if err := s.ExecuteInvokes(cmd, args, initCmdNoScope); err != nil { return nil, cobra.ShellCompDirectiveError } @@ -234,7 +234,7 @@ func (c *CmdWScope) useProjectCompletion( return nil, cobra.ShellCompDirectiveError } - return completions.Projects(ctx, c.Rig, toComplete, c.Scope) + return completions.Projects(ctx, c.Rig, toComplete) } func (c *CmdWScope) useEnvironmentCompletion( @@ -248,5 +248,5 @@ func (c *CmdWScope) useEnvironmentCompletion( return nil, cobra.ShellCompDirectiveError } - return completions.Environments(ctx, c.Rig, toComplete, c.Scope) + return completions.Environments(ctx, c.Rig, toComplete) } diff --git a/cmd/rig/cmd/environment/setup.go b/cmd/rig/cmd/environment/setup.go index 0e31286ac..7fd08ef77 100644 --- a/cmd/rig/cmd/environment/setup.go +++ b/cmd/rig/cmd/environment/setup.go @@ -104,5 +104,5 @@ func (c *Cmd) completeEnvironment( return nil, cobra.ShellCompDirectiveError } - return completions.Environments(ctx, c.Rig, toComplete, c.Scope) + return completions.Environments(ctx, c.Rig, toComplete) } diff --git a/cmd/rig/cmd/group/create.go b/cmd/rig/cmd/group/create.go index a679b02aa..649c7d918 100644 --- a/cmd/rig/cmd/group/create.go +++ b/cmd/rig/cmd/group/create.go @@ -38,6 +38,6 @@ func (c *Cmd) create(ctx context.Context, cmd *cobra.Command, args []string) err return err } - cmd.Println("Created group:", groupID, res.Msg.GetGroup().GetGroupId()) + cmd.Println("Created group", res.Msg.GetGroup().GetGroupId()) return nil } diff --git a/cmd/rig/cmd/group/list.go b/cmd/rig/cmd/group/list.go index 5fd0e9366..b3286306b 100644 --- a/cmd/rig/cmd/group/list.go +++ b/cmd/rig/cmd/group/list.go @@ -13,7 +13,7 @@ import ( "github.com/spf13/cobra" ) -func (c *Cmd) get(ctx context.Context, cmd *cobra.Command, args []string) error { +func (c *Cmd) list(ctx context.Context, cmd *cobra.Command, args []string) error { if len(args) > 0 { identifier := "" if len(args) > 0 { diff --git a/cmd/rig/cmd/group/setup.go b/cmd/rig/cmd/group/setup.go index 75cfd6534..f07920808 100644 --- a/cmd/rig/cmd/group/setup.go +++ b/cmd/rig/cmd/group/setup.go @@ -6,14 +6,15 @@ import ( "strings" "connectrpc.com/connect" - "github.com/rigdev/rig-go-api/api/v1/group" "github.com/rigdev/rig-go-api/api/v1/service_account" "github.com/rigdev/rig-go-api/api/v1/user" "github.com/rigdev/rig-go-api/model" "github.com/rigdev/rig-go-sdk" "github.com/rigdev/rig/cmd/common" + "github.com/rigdev/rig/cmd/rig/cmd/completions" "github.com/rigdev/rig/cmd/rig/services/auth" "github.com/rigdev/rig/pkg/cli" + "github.com/rigdev/rig/pkg/cli/scope" "github.com/spf13/cobra" "go.uber.org/fx" ) @@ -28,18 +29,23 @@ type Cmd struct { Rig rig.Client Prompter common.Prompter + Scope scope.Scope } var cmd Cmd func initCmd(c Cmd) { - cmd = c + cmd.Rig = c.Rig + cmd.Scope = c.Scope + cmd.Prompter = c.Prompter } func Setup(parent *cobra.Command, s *cli.SetupContext) { group := &cobra.Command{ - Use: "group", - Short: "Manage groups", + Use: "group", + Short: "Manage role groups", + Long: "Groups are a way to organize users and service accounts into groups with certain roles, where " + + "the roles assigned to a group are inherited by all members of the group.", PersistentPreRunE: s.MakeInvokePreRunE(initCmd), Annotations: map[string]string{ auth.OmitProject: "", @@ -52,7 +58,7 @@ func Setup(parent *cobra.Command, s *cli.SetupContext) { Use: "create [group-id]", Short: "Create a new group", RunE: cli.CtxWrap(cmd.create), - Args: cobra.NoArgs, + Args: cobra.MaximumNArgs(1), } group.AddCommand(create) @@ -84,7 +90,7 @@ func Setup(parent *cobra.Command, s *cli.SetupContext) { Use: "list", Short: "list groups", Aliases: []string{"ls"}, - RunE: cli.CtxWrap(cmd.get), + RunE: cli.CtxWrap(cmd.list), } list.Flags().IntVarP(&limit, "limit", "l", 10, "limit the number of groups to return") list.Flags().IntVar(&offset, "offset", 0, "offset the number of groups to return") @@ -158,29 +164,7 @@ func (c *Cmd) completions( return nil, cobra.ShellCompDirectiveError } - completions := []string{} - resp, err := c.Rig.Group().List(ctx, &connect.Request[group.ListRequest]{ - Msg: &group.ListRequest{}, - }) - if err != nil { - return nil, cobra.ShellCompDirectiveError - } - - for _, g := range resp.Msg.GetGroups() { - if strings.HasPrefix(g.GetGroupId(), toComplete) { - completions = append(completions, formatGroup(g)) - } - } - - if len(completions) == 0 { - return nil, cobra.ShellCompDirectiveError - } - - return completions, cobra.ShellCompDirectiveNoFileComp -} - -func formatGroup(g *group.Group) string { - return fmt.Sprintf("%s\t (#Members: %v)", g.GetGroupId(), g.GetNumMembers()) + return completions.Groups(ctx, c.Rig, toComplete) } func (c *Cmd) memberCompletions( diff --git a/cmd/rig/cmd/noop/setup.go b/cmd/rig/cmd/noop/setup.go index fa47ff2fe..daf5eeb98 100644 --- a/cmd/rig/cmd/noop/setup.go +++ b/cmd/rig/cmd/noop/setup.go @@ -24,10 +24,21 @@ func initCmd(c Cmd) { cc = c } +type CmdNoRig struct { + fx.In + + Prompter common.Prompter +} + +var ccNoRig CmdNoRig + +func initCmdNoRig(c CmdNoRig) { + ccNoRig = c +} + func Setup(parent *cobra.Command, s *cli.SetupContext) { cmd := &cobra.Command{ - Use: "noop", - PersistentPreRunE: s.MakeInvokePreRunE(initCmd), + Use: "noop", } cmd1 := &cobra.Command{ Use: "cmd1", @@ -49,6 +60,19 @@ func Setup(parent *cobra.Command, s *cli.SetupContext) { } cmd.AddCommand(cmd2) + cmd3 := &cobra.Command{ + Use: "cmd3", + PersistentPreRunE: s.MakeInvokePreRunE(initCmdNoRig), + Annotations: map[string]string{ + auth.OmitUser: "", + auth.OmitEnvironment: "", + auth.OmitCapsule: "", + auth.OmitProject: "", + }, + RunE: ccNoRig.noop, + } + cmd.AddCommand(cmd3) + parent.AddCommand(cmd) } @@ -56,3 +80,8 @@ func (c *Cmd) noop(_ *cobra.Command, _ []string) error { fmt.Println("noop") return nil } + +func (c *CmdNoRig) noop(_ *cobra.Command, _ []string) error { + fmt.Println("noop") + return nil +} diff --git a/cmd/rig/cmd/role/assign.go b/cmd/rig/cmd/role/assign.go new file mode 100644 index 000000000..b215e0c24 --- /dev/null +++ b/cmd/rig/cmd/role/assign.go @@ -0,0 +1,69 @@ +package role + +import ( + "context" + + "connectrpc.com/connect" + "github.com/rigdev/rig-go-api/api/v1/group" + "github.com/rigdev/rig-go-api/api/v1/role" + "github.com/spf13/cobra" +) + +func (c *Cmd) assign(ctx context.Context, cmd *cobra.Command, args []string) error { + var roleID string + var groupID string + // Get role ID + if len(args) == 0 { + resp, err := c.Rig.Role().List(ctx, connect.NewRequest(&role.ListRequest{})) + if err != nil { + return err + } + + var roleIDs []string + for _, r := range resp.Msg.GetRoles() { + roleIDs = append(roleIDs, r.GetRoleId()) + } + + _, roleID, err = c.Prompter.Select("Select role:", roleIDs) + if err != nil { + return err + } + } else { + roleID = args[0] + } + + // Get group ID + if len(args) < 2 { + resp, err := c.Rig.Group().List(ctx, connect.NewRequest(&group.ListRequest{})) + if err != nil { + return err + } + + var groupIDs []string + for _, g := range resp.Msg.GetGroups() { + groupIDs = append(groupIDs, g.GetGroupId()) + } + + _, groupID, err = c.Prompter.Select("Select group:", groupIDs) + if err != nil { + return err + } + } else { + groupID = args[1] + } + + if _, err := c.Rig.Role().Assign(ctx, connect.NewRequest(&role.AssignRequest{ + RoleId: roleID, + EntityId: &role.EntityID{ + Kind: &role.EntityID_GroupId{ + GroupId: groupID, + }, + }, + })); err != nil { + return err + } + + cmd.Println("Assigned role:", roleID, "to group:", groupID) + + return nil +} diff --git a/cmd/rig/cmd/role/create.go b/cmd/rig/cmd/role/create.go new file mode 100644 index 000000000..23d6b009a --- /dev/null +++ b/cmd/rig/cmd/role/create.go @@ -0,0 +1,67 @@ +package role + +import ( + "context" + "fmt" + + "connectrpc.com/connect" + "github.com/rigdev/rig-go-api/api/v1/role" + "github.com/rigdev/rig-go-api/model" + "github.com/rigdev/rig/cmd/common" + "github.com/rigdev/rig/pkg/rbac" + "github.com/spf13/cobra" +) + +func (c *Cmd) create(ctx context.Context, cmd *cobra.Command, args []string) error { + roleID := "" + var err error + if len(args) > 0 { + roleID = args[0] + } else { + roleID, err = c.Prompter.Input("Enter Role ID:", common.ValidateNonEmptyOpt) + if err != nil { + return err + } + } + + var permissions []*role.Permission + switch roleType { + case "admin": + permissions = rbac.GetAdminPermissions(project, environment) + case "owner": + permissions = rbac.GetOwnerPermissions(project, environment) + case "developer": + permissions = rbac.GetDeveloperPermissions(project, environment) + case "viewer": + permissions = rbac.GetViewerPermissions(project, environment) + default: + return fmt.Errorf("invalid role type: %v", roleType) + } + + if _, err = c.Rig.Role().Create(ctx, connect.NewRequest(&role.CreateRequest{ + RoleId: roleID, + Permissions: permissions, + })); err != nil { + return err + } + + if _, err = c.Rig.Role().Update(ctx, connect.NewRequest(&role.UpdateRequest{ + RoleId: roleID, + Updates: []*role.Update{ + { + Update: &role.Update_SetMetadata{ + SetMetadata: &model.Metadata{ + Key: "roleType", + Value: []byte(roleType), + }, + }, + }, + }, + })); err != nil { + return err + } + + cmd.Printf("Created role %s of type %s with access to project %s and environment %s \n", + roleID, roleType, project, environment) + return nil +} diff --git a/cmd/rig/cmd/role/delete.go b/cmd/rig/cmd/role/delete.go new file mode 100644 index 000000000..f066f3729 --- /dev/null +++ b/cmd/rig/cmd/role/delete.go @@ -0,0 +1,40 @@ +package role + +import ( + "context" + + "connectrpc.com/connect" + "github.com/rigdev/rig-go-api/api/v1/role" + "github.com/spf13/cobra" +) + +func (c *Cmd) delete(ctx context.Context, cmd *cobra.Command, args []string) error { + roleID := "" + if len(args) > 0 { + roleID = args[0] + } else { + resp, err := c.Rig.Role().List(ctx, connect.NewRequest(&role.ListRequest{})) + if err != nil { + return err + } + + var roleIDs []string + for _, r := range resp.Msg.GetRoles() { + roleIDs = append(roleIDs, r.GetRoleId()) + } + + _, roleID, err = c.Prompter.Select("Select role to delete", roleIDs) + if err != nil { + return err + } + } + + if _, err := c.Rig.Role().Delete(ctx, connect.NewRequest(&role.DeleteRequest{ + RoleId: roleID, + })); err != nil { + return err + } + + cmd.Println("Deleted role:", roleID) + return nil +} diff --git a/cmd/rig/cmd/role/get.go b/cmd/rig/cmd/role/get.go new file mode 100644 index 000000000..748cb774d --- /dev/null +++ b/cmd/rig/cmd/role/get.go @@ -0,0 +1,64 @@ +package role + +import ( + "context" + "fmt" + + "connectrpc.com/connect" + "github.com/jedib0t/go-pretty/v6/table" + "github.com/rigdev/rig-go-api/api/v1/role" + "github.com/rigdev/rig/cmd/common" + "github.com/rigdev/rig/cmd/rig/cmd/flags" + "github.com/spf13/cobra" +) + +func (c *Cmd) get(ctx context.Context, cmd *cobra.Command, args []string) error { + roleID := "" + if len(args) > 0 { + roleID = args[0] + } else { + resp, err := c.Rig.Role().List(ctx, connect.NewRequest(&role.ListRequest{})) + if err != nil { + return err + } + + var roleIDs []string + for _, r := range resp.Msg.GetRoles() { + roleIDs = append(roleIDs, r.GetRoleId()) + } + + _, roleID, err = c.Prompter.Select("Select role to get", roleIDs) + if err != nil { + return err + } + } + + resp, err := c.Rig.Role().Get(ctx, connect.NewRequest(&role.GetRequest{ + RoleId: roleID, + })) + if err != nil { + return err + } + + if flags.Flags.OutputType != common.OutputTypePretty { + return common.FormatPrint(resp.Msg.GetRole(), flags.Flags.OutputType) + } + + roleType := resp.Msg.GetRole().GetRoleId() + if v, ok := resp.Msg.GetRole().GetMetadata()["roleType"]; ok { + roleType = string(v) + } + cmd.Println(fmt.Sprintf("Role ID: %s\nRole Type: %s\nProject: %s\nEnvironment: %s", resp.Msg.GetRole().GetRoleId(), + roleType, + resp.Msg.GetRole().GetPermissions()[0].GetScope().GetProject(), + resp.Msg.GetRole().GetPermissions()[0].GetScope().GetEnvironment()), + ) + t := table.NewWriter() + t.AppendHeader(table.Row{"Permissions", "Action", "Resource"}) + for i, p := range resp.Msg.GetRole().GetPermissions() { + t.AppendRow(table.Row{i + 1, p.GetAction(), p.GetScope().GetResource()}) + } + cmd.Println(t.Render()) + + return nil +} diff --git a/cmd/rig/cmd/role/list.go b/cmd/rig/cmd/role/list.go new file mode 100644 index 000000000..710e47353 --- /dev/null +++ b/cmd/rig/cmd/role/list.go @@ -0,0 +1,49 @@ +package role + +import ( + "context" + + "connectrpc.com/connect" + "github.com/jedib0t/go-pretty/v6/table" + "github.com/rigdev/rig-go-api/api/v1/role" + "github.com/rigdev/rig-go-api/model" + "github.com/rigdev/rig/cmd/common" + "github.com/rigdev/rig/cmd/rig/cmd/flags" + "github.com/spf13/cobra" +) + +func (c *Cmd) list(ctx context.Context, cmd *cobra.Command, _ []string) error { + resp, err := c.Rig.Role().List(ctx, connect.NewRequest(&role.ListRequest{ + Pagination: &model.Pagination{ + Offset: uint32(offset), + Limit: uint32(limit), + }, + })) + if err != nil { + return err + } + + if flags.Flags.OutputType != common.OutputTypePretty { + return common.FormatPrint(resp.Msg.GetRoles(), flags.Flags.OutputType) + } + + t := table.NewWriter() + t.AppendHeader(table.Row{"Roles", "ID", "Project", "Environment", "Role Type"}) + for i, r := range resp.Msg.GetRoles() { + project := "*" + environment := "*" + if r.GetPermissions() != nil && len(r.GetPermissions()) > 0 { + project = r.GetPermissions()[0].GetScope().GetProject() + environment = r.GetPermissions()[0].GetScope().GetEnvironment() + } + + roleType, ok := r.Metadata["roleType"] + if !ok { + roleType = []byte(r.GetRoleId()) + } + + t.AppendRow(table.Row{i + 1, r.GetRoleId(), project, environment, string(roleType)}) + } + cmd.Println(t.Render()) + return nil +} diff --git a/cmd/rig/cmd/role/list_for_group.go b/cmd/rig/cmd/role/list_for_group.go new file mode 100644 index 000000000..d0bd004c0 --- /dev/null +++ b/cmd/rig/cmd/role/list_for_group.go @@ -0,0 +1,79 @@ +package role + +import ( + "context" + + "connectrpc.com/connect" + "github.com/jedib0t/go-pretty/v6/table" + "github.com/rigdev/rig-go-api/api/v1/group" + "github.com/rigdev/rig-go-api/api/v1/role" + "github.com/rigdev/rig-go-api/model" + "github.com/spf13/cobra" +) + +// ListForGroup lists all roles for a group +func (c *Cmd) listRolesForGroup(ctx context.Context, cmd *cobra.Command, args []string) error { + groupID := "" + if len(args) > 0 { + groupID = args[0] + } else { + groupRes, err := c.Rig.Group().List(ctx, connect.NewRequest(&group.ListRequest{})) + if err != nil { + return err + } + + var groupIDs []string + for _, g := range groupRes.Msg.GetGroups() { + groupIDs = append(groupIDs, g.GetGroupId()) + } + + _, groupID, err = c.Prompter.Select("Select group to list roles for", groupIDs) + if err != nil { + return err + } + } + + rolesResp, err := c.Rig.Role().ListForEntity(ctx, connect.NewRequest(&role.ListForEntityRequest{ + EntityId: &role.EntityID{ + Kind: &role.EntityID_GroupId{ + GroupId: groupID, + }, + }, + Pagination: &model.Pagination{ + Offset: uint32(offset), + Limit: uint32(limit), + }, + })) + if err != nil { + return err + } + + t := table.NewWriter() + t.AppendHeader(table.Row{"Roles", "ID", "Project", "Environment", "Role Type"}) + for i, roleID := range rolesResp.Msg.GetRoleIds() { + roleResp, err := c.Rig.Role().Get(ctx, connect.NewRequest(&role.GetRequest{ + RoleId: roleID, + })) + if err != nil { + return err + } + + r := roleResp.Msg.GetRole() + + project := "*" + environment := "*" + if r.GetPermissions() != nil && len(r.GetPermissions()) > 0 { + project = r.GetPermissions()[0].GetScope().GetProject() + environment = r.GetPermissions()[0].GetScope().GetEnvironment() + } + + roleType, ok := r.Metadata["roleType"] + if !ok { + roleType = []byte(r.GetRoleId()) + } + + t.AppendRow(table.Row{i + 1, r.GetRoleId(), project, environment, string(roleType)}) + } + cmd.Println(t.Render()) + return nil +} diff --git a/cmd/rig/cmd/role/list_for_role.go b/cmd/rig/cmd/role/list_for_role.go new file mode 100644 index 000000000..d385cf3d8 --- /dev/null +++ b/cmd/rig/cmd/role/list_for_role.go @@ -0,0 +1,54 @@ +package role + +import ( + "context" + + "connectrpc.com/connect" + "github.com/rigdev/rig-go-api/api/v1/role" + "github.com/rigdev/rig-go-api/model" + "github.com/spf13/cobra" +) + +func (c *Cmd) listGroupsForRole(ctx context.Context, cmd *cobra.Command, args []string) error { + roleID := "" + if len(args) > 0 { + roleID = args[0] + } else { + resp, err := c.Rig.Role().List(ctx, connect.NewRequest(&role.ListRequest{})) + if err != nil { + return err + } + + var roleIDs []string + for _, r := range resp.Msg.GetRoles() { + roleIDs = append(roleIDs, r.GetRoleId()) + } + + _, roleID, err = c.Prompter.Select("Select role to list groups for", roleIDs) + if err != nil { + return err + } + } + + groupsResp, err := c.Rig.Role().ListAssignees(ctx, connect.NewRequest(&role.ListAssigneesRequest{ + RoleId: roleID, + Pagination: &model.Pagination{ + Offset: uint32(offset), + Limit: uint32(limit), + }, + })) + if err != nil { + return err + } + + if len(groupsResp.Msg.GetEntityIds()) == 0 { + cmd.Println("No groups assigned to role") + return nil + } + + for _, ID := range groupsResp.Msg.GetEntityIds() { + cmd.Println("Group: ", ID) + } + + return nil +} diff --git a/cmd/rig/cmd/role/revoke.go b/cmd/rig/cmd/role/revoke.go new file mode 100644 index 000000000..f63678655 --- /dev/null +++ b/cmd/rig/cmd/role/revoke.go @@ -0,0 +1,69 @@ +package role + +import ( + "context" + + "connectrpc.com/connect" + "github.com/rigdev/rig-go-api/api/v1/group" + "github.com/rigdev/rig-go-api/api/v1/role" + "github.com/spf13/cobra" +) + +func (c *Cmd) revoke(ctx context.Context, cmd *cobra.Command, args []string) error { + var roleID string + var groupID string + // Get role ID + if len(args) == 0 { + resp, err := c.Rig.Role().List(ctx, connect.NewRequest(&role.ListRequest{})) + if err != nil { + return err + } + + var roleIDs []string + for _, r := range resp.Msg.GetRoles() { + roleIDs = append(roleIDs, r.GetRoleId()) + } + + _, roleID, err = c.Prompter.Select("Select role:", roleIDs) + if err != nil { + return err + } + } else { + roleID = args[0] + } + + // Get group ID + if len(args) < 2 { + resp, err := c.Rig.Group().List(ctx, connect.NewRequest(&group.ListRequest{})) + if err != nil { + return err + } + + var groupIDs []string + for _, g := range resp.Msg.GetGroups() { + groupIDs = append(groupIDs, g.GetGroupId()) + } + + _, groupID, err = c.Prompter.Select("Select group:", groupIDs) + if err != nil { + return err + } + } else { + groupID = args[1] + } + + if _, err := c.Rig.Role().Revoke(ctx, connect.NewRequest(&role.RevokeRequest{ + RoleId: roleID, + EntityId: &role.EntityID{ + Kind: &role.EntityID_GroupId{ + GroupId: groupID, + }, + }, + })); err != nil { + return err + } + + cmd.Println("Revoked role:", roleID, "from group:", groupID) + + return nil +} diff --git a/cmd/rig/cmd/role/setup.go b/cmd/rig/cmd/role/setup.go new file mode 100644 index 000000000..a9e124178 --- /dev/null +++ b/cmd/rig/cmd/role/setup.go @@ -0,0 +1,303 @@ +package role + +import ( + "context" + "fmt" + "os" + "strings" + + "connectrpc.com/connect" + "github.com/rigdev/rig-go-api/api/v1/role" + "github.com/rigdev/rig-go-sdk" + "github.com/rigdev/rig/cmd/common" + "github.com/rigdev/rig/cmd/rig/cmd/completions" + "github.com/rigdev/rig/cmd/rig/services/auth" + "github.com/rigdev/rig/pkg/cli" + "github.com/rigdev/rig/pkg/cli/scope" + "github.com/spf13/cobra" + "go.uber.org/fx" +) + +type Cmd struct { + fx.In + + Rig rig.Client + Prompter common.Prompter + Scope scope.Scope +} + +var cmd Cmd + +func initCmd(c Cmd) { + cmd.Rig = c.Rig + cmd.Scope = c.Scope + cmd.Prompter = c.Prompter +} + +var ( + limit int + offset int +) + +var ( + roleType string + project string + environment string + + updateRoleType string + updateProject string + updateEnvironment string +) + +func Setup(parent *cobra.Command, s *cli.SetupContext) { + roles := &cobra.Command{ + Use: "role", + Short: "Manage roles", + PersistentPreRunE: s.MakeInvokePreRunE(initCmd), + Annotations: map[string]string{ + auth.OmitProject: "", + auth.OmitEnvironment: "", + }, + GroupID: common.AuthGroupID, + } + + create := &cobra.Command{ + Use: "create [role-id]", + Short: "Create a new role", + RunE: cli.CtxWrap(cmd.create), + Args: cobra.MaximumNArgs(1), + } + create.Flags().StringVarP(&roleType, "type", "t", "owner", + "Select the role type to create. Must be one of (admin, owner, developer, viewer)") + create.Flags().StringVar(&project, "project", "*", "Select the project to give the roll access to. "+ + "If none is provided, the role will have access to all projects") + create.Flags().StringVar(&environment, "environment", "*", "Select the environment to give the role access to. "+ + "If none is provided, the role will have access to all environments") + + if err := create.RegisterFlagCompletionFunc("type", common.RoleCompletions); err != nil { + fmt.Println(err) + os.Exit(1) + } + + if err := create.RegisterFlagCompletionFunc("project", + cli.HackCtxWrapCompletion(cmd.completeProject, s)); err != nil { + fmt.Println(err) + os.Exit(1) + } + + if err := create.RegisterFlagCompletionFunc("environment", + cli.HackCtxWrapCompletion(cmd.completeEnvironment, s)); err != nil { + fmt.Println(err) + os.Exit(1) + } + + roles.AddCommand(create) + + deleteCmd := &cobra.Command{ + Use: "delete [role-id]", + Short: "Delete a role", + Args: cobra.MaximumNArgs(1), + ValidArgsFunction: common.Complete( + cli.HackCtxWrapCompletion(cmd.completeRole, s), + common.MaxArgsCompletionFilter(1), + ), + RunE: cli.CtxWrap(cmd.delete), + } + roles.AddCommand(deleteCmd) + + list := &cobra.Command{ + Use: "list", + Short: "List roles", + RunE: cli.CtxWrap(cmd.list), + Args: cobra.NoArgs, + } + list.Flags().IntVarP(&limit, "limit", "l", 10, "limit the number of groups to return") + list.Flags().IntVar(&offset, "offset", 0, "offset the number of groups to return") + roles.AddCommand(list) + + listRolesForGroup := &cobra.Command{ + Use: "list-for-group [group-id]", + Short: "List roles for a group", + Args: cobra.MaximumNArgs(1), + RunE: cli.CtxWrap(cmd.listRolesForGroup), + ValidArgsFunction: common.Complete( + cli.HackCtxWrapCompletion(cmd.completeGroup, s), + common.MaxArgsCompletionFilter(1), + ), + } + listRolesForGroup.Flags().IntVarP(&limit, "limit", "l", 10, "limit the number of groups to return") + listRolesForGroup.Flags().IntVar(&offset, "offset", 0, "offset the number of groups to return") + roles.AddCommand(listRolesForGroup) + + listGroupsForRole := &cobra.Command{ + Use: "list-for-role [role-id]", + Short: "List groups for a role", + Args: cobra.MaximumNArgs(1), + RunE: cli.CtxWrap(cmd.listGroupsForRole), + ValidArgsFunction: common.Complete( + cli.HackCtxWrapCompletion(cmd.completeRole, s), + common.MaxArgsCompletionFilter(1), + ), + } + listGroupsForRole.Flags().IntVarP(&limit, "limit", "l", 10, "limit the number of groups to return") + listGroupsForRole.Flags().IntVar(&offset, "offset", 0, "offset the number of groups to return") + roles.AddCommand(listGroupsForRole) + + assign := &cobra.Command{ + Use: "assign [role-id] [group-id]", + Short: "Assign a role to a group", + RunE: cli.CtxWrap(cmd.assign), + Args: cobra.MaximumNArgs(2), + ValidArgsFunction: common.ChainCompletions( + []int{1, 2}, + cli.HackCtxWrapCompletion(cmd.completeRole, s), + cli.HackCtxWrapCompletion(cmd.completeGroup, s), + ), + } + roles.AddCommand(assign) + + revoke := &cobra.Command{ + Use: "revoke [role-id] [group-id]", + Short: "Revoke a role from a group", + RunE: cli.CtxWrap(cmd.revoke), + Args: cobra.MaximumNArgs(2), + ValidArgsFunction: common.ChainCompletions( + []int{1, 2}, + cli.HackCtxWrapCompletion(cmd.completeRole, s), + cli.HackCtxWrapCompletion(cmd.completeGroup, s), + ), + } + roles.AddCommand(revoke) + + get := &cobra.Command{ + Use: "get [role-id]", + Short: "Get a role", + RunE: cli.CtxWrap(cmd.get), + Args: cobra.MaximumNArgs(1), + ValidArgsFunction: common.Complete( + cli.HackCtxWrapCompletion(cmd.completeRole, s), + common.MaxArgsCompletionFilter(1), + ), + } + roles.AddCommand(get) + + update := &cobra.Command{ + Use: "update [role-id]", + Short: "Update a role", + RunE: cli.CtxWrap(cmd.update), + Args: cobra.MaximumNArgs(1), + ValidArgsFunction: common.Complete( + cli.HackCtxWrapCompletion(cmd.completeRole, s), + common.MaxArgsCompletionFilter(1), + ), + } + update.Flags().StringVarP(&updateRoleType, "type", "t", "", + "Select the role type to update. Must be one of (admin, owner, developer, viewer)") + update.Flags().StringVar(&updateProject, "project", "", "Select the project to give the roll access to. "+ + "If none is provided, the role will have access to all projects") + update.Flags().StringVar(&updateEnvironment, "environment", "", "Select the environment to give the role access to. "+ + "If none is provided, the role will have access to all environments") + + if err := update.RegisterFlagCompletionFunc("type", common.RoleCompletions); err != nil { + fmt.Println(err) + os.Exit(1) + } + + if err := update.RegisterFlagCompletionFunc("project", + cli.HackCtxWrapCompletion(cmd.completeProject, s)); err != nil { + fmt.Println(err) + os.Exit(1) + } + + if err := update.RegisterFlagCompletionFunc("environment", + cli.HackCtxWrapCompletion(cmd.completeEnvironment, s)); err != nil { + fmt.Println(err) + os.Exit(1) + } + + roles.AddCommand(update) + + parent.AddCommand(roles) +} + +func formatRole(r *role.Role) string { + roleType := r.GetRoleId() + if t, ok := r.GetMetadata()["roleType"]; ok { + roleType = string(t) + } + + project := r.GetPermissions()[0].GetScope().GetProject() + environment := r.GetPermissions()[0].GetScope().GetEnvironment() + + return fmt.Sprintf("%s\t (Type: %s, Project: %s, Environment: %s)", r.GetRoleId(), roleType, project, environment) +} + +func (c *Cmd) completeRole( + ctx context.Context, + cmd *cobra.Command, + args []string, + toComplete string, + s *cli.SetupContext, +) ([]string, cobra.ShellCompDirective) { + if err := s.ExecuteInvokes(cmd, args, initCmd); err != nil { + return nil, cobra.ShellCompDirectiveError + } + + rolesResp, err := c.Rig.Role().List(ctx, connect.NewRequest( + &role.ListRequest{}, + )) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + roles := []string{} + for _, r := range rolesResp.Msg.GetRoles() { + if strings.HasPrefix(r.GetRoleId(), toComplete) { + roles = append(roles, formatRole(r)) + } + } + + return roles, cobra.ShellCompDirectiveNoFileComp +} + +func (c *Cmd) completeGroup( + ctx context.Context, + cmd *cobra.Command, + args []string, + toComplete string, + s *cli.SetupContext, +) ([]string, cobra.ShellCompDirective) { + if err := s.ExecuteInvokes(cmd, args, initCmd); err != nil { + return nil, cobra.ShellCompDirectiveError + } + + return completions.Groups(ctx, c.Rig, toComplete) +} + +func (c *Cmd) completeProject( + ctx context.Context, + cmd *cobra.Command, + args []string, + toComplete string, + s *cli.SetupContext, +) ([]string, cobra.ShellCompDirective) { + if err := s.ExecuteInvokes(cmd, args, initCmd); err != nil { + return nil, cobra.ShellCompDirectiveError + } + + return completions.Projects(ctx, c.Rig, toComplete) +} + +func (c *Cmd) completeEnvironment( + ctx context.Context, + cmd *cobra.Command, + args []string, + toComplete string, + s *cli.SetupContext, +) ([]string, cobra.ShellCompDirective) { + if err := s.ExecuteInvokes(cmd, args, initCmd); err != nil { + return nil, cobra.ShellCompDirectiveError + } + + return completions.Environments(ctx, c.Rig, toComplete) +} diff --git a/cmd/rig/cmd/role/update.go b/cmd/rig/cmd/role/update.go new file mode 100644 index 000000000..25979f7d1 --- /dev/null +++ b/cmd/rig/cmd/role/update.go @@ -0,0 +1,210 @@ +package role + +import ( + "context" + "fmt" + + "connectrpc.com/connect" + environment_api "github.com/rigdev/rig-go-api/api/v1/environment" + project_api "github.com/rigdev/rig-go-api/api/v1/project" + "github.com/rigdev/rig-go-api/api/v1/role" + "github.com/rigdev/rig-go-api/model" + "github.com/rigdev/rig/pkg/rbac" + "github.com/spf13/cobra" +) + +func (c *Cmd) update(ctx context.Context, cmd *cobra.Command, args []string) error { + roleID := "" + if len(args) > 0 { + roleID = args[0] + } else { + resp, err := c.Rig.Role().List(ctx, connect.NewRequest(&role.ListRequest{})) + if err != nil { + return err + } + + var roleIDs []string + for _, r := range resp.Msg.GetRoles() { + roleIDs = append(roleIDs, r.GetRoleId()) + } + + _, roleID, err = c.Prompter.Select("Select role to update", roleIDs) + if err != nil { + return err + } + } + + roleResp, err := c.Rig.Role().Get(ctx, connect.NewRequest(&role.GetRequest{ + RoleId: roleID, + })) + if err != nil { + return err + } + + currRollType := "" + currRollTypeBytes, ok := roleResp.Msg.GetRole().GetMetadata()["roleType"] + if ok { + currRollType = string(currRollTypeBytes) + } + + currProject := roleResp.Msg.GetRole().GetPermissions()[0].GetScope().GetProject() + currEnv := roleResp.Msg.GetRole().GetPermissions()[0].GetScope().GetEnvironment() + + // Nothing set to update so we prompt + if updateRoleType == "" && updateProject == "" && updateEnvironment == "" { + for { + cmd.Printf("Current Role Config:\nRole Type: %v\nProject: %v\nEnvironment: %v\n", currRollType, currProject, currEnv) + choices := []string{"Role Type", "Project", "Environment", "Done"} + _, choice, err := c.Prompter.Select("Select what to update", choices) + if err != nil { + return err + } + + switch choice { + case "Role Type": + roleChoices := []string{"admin", "owner", "developer", "viewer"} + // remove the current role type from choices + for i, r := range roleChoices { + if r == currRollType { + roleChoices = append(roleChoices[:i], roleChoices[i+1:]...) + break + } + } + + _, updateRoleType, err := c.Prompter.Select("Select the role type to update to", roleChoices) + if err != nil { + return err + } + currRollType = updateRoleType + case "Project": + projectResp, err := c.Rig.Project().List(ctx, connect.NewRequest(&project_api.ListRequest{})) + if err != nil { + return err + } + + projectIDsChoices := []string{"*"} + for _, p := range projectResp.Msg.GetProjects() { + projectIDsChoices = append(projectIDsChoices, p.GetProjectId()) + } + + // remove the current project from choices + for i, p := range projectIDsChoices { + if p == currProject { + projectIDsChoices = append(projectIDsChoices[:i], projectIDsChoices[i+1:]...) + break + } + } + + _, updateProject, err = c.Prompter.Select("Select the project to update to", projectIDsChoices) + if err != nil { + return err + } + currProject = updateProject + case "Environment": + envResp, err := c.Rig.Environment().List(ctx, connect.NewRequest(&environment_api.ListRequest{})) + if err != nil { + return err + } + + envIDsChoices := []string{"*"} + for _, e := range envResp.Msg.GetEnvironments() { + envIDsChoices = append(envIDsChoices, e.GetEnvironmentId()) + } + + // remove the current environment from choices + for i, e := range envIDsChoices { + if e == currEnv { + envIDsChoices = append(envIDsChoices[:i], envIDsChoices[i+1:]...) + break + } + } + + _, updateEnvironment, err = c.Prompter.Select("Select the environment to update to", envIDsChoices) + if err != nil { + return err + } + currEnv = updateEnvironment + case "Done": + goto done + } + } + done: + } + + if updateProject == "" { + updateProject = currProject + } + if updateEnvironment == "" { + updateEnvironment = currEnv + } + if updateRoleType == "" { + updateRoleType = currRollType + } + + var permissions []*role.Permission + switch updateRoleType { + case "admin": + permissions = rbac.GetAdminPermissions(updateProject, updateEnvironment) + case "owner": + permissions = rbac.GetOwnerPermissions(updateProject, updateEnvironment) + case "developer": + permissions = rbac.GetDeveloperPermissions(updateProject, updateEnvironment) + case "viewer": + permissions = rbac.GetViewerPermissions(updateProject, updateEnvironment) + default: + return fmt.Errorf("invalid role type: %v", updateRoleType) + } + + assigneesResp, err := c.Rig.Role().ListAssignees(ctx, connect.NewRequest(&role.ListAssigneesRequest{ + RoleId: roleID, + })) + if err != nil { + return err + } + + if _, err := c.Rig.Role().Delete(ctx, connect.NewRequest(&role.DeleteRequest{ + RoleId: roleID, + })); err != nil { + return err + } + + if _, err := c.Rig.Role().Create(ctx, connect.NewRequest(&role.CreateRequest{ + RoleId: roleID, + Permissions: permissions, + })); err != nil { + return err + } + + if _, err = c.Rig.Role().Update(ctx, connect.NewRequest(&role.UpdateRequest{ + RoleId: roleID, + Updates: []*role.Update{ + { + Update: &role.Update_SetMetadata{ + SetMetadata: &model.Metadata{ + Key: "roleType", + Value: []byte(updateRoleType), + }, + }, + }, + }, + })); err != nil { + return err + } + + for _, a := range assigneesResp.Msg.GetEntityIds() { + if _, err := c.Rig.Role().Assign(ctx, connect.NewRequest(&role.AssignRequest{ + RoleId: roleID, + EntityId: &role.EntityID{ + Kind: &role.EntityID_GroupId{ + GroupId: a, + }, + }, + })); err != nil { + return err + } + } + + cmd.Printf("Role %s updated successfully with role: %s, project: %s and environment: %s\n", + roleID, updateRoleType, updateProject, updateEnvironment) + return nil +} diff --git a/cmd/rig/cmd/root.go b/cmd/rig/cmd/root.go index fa3a22e95..7d7a45b39 100644 --- a/cmd/rig/cmd/root.go +++ b/cmd/rig/cmd/root.go @@ -24,6 +24,7 @@ import ( "github.com/rigdev/rig/cmd/rig/cmd/group" "github.com/rigdev/rig/cmd/rig/cmd/noop" "github.com/rigdev/rig/cmd/rig/cmd/project" + "github.com/rigdev/rig/cmd/rig/cmd/role" "github.com/rigdev/rig/cmd/rig/cmd/serviceaccount" "github.com/rigdev/rig/cmd/rig/cmd/user" auth_service "github.com/rigdev/rig/cmd/rig/services/auth" @@ -162,10 +163,11 @@ func Run(s *cli.SetupContext) error { config.Setup(rootCmd, s) project.Setup(rootCmd, s) environment.Setup(rootCmd, s) + role.Setup(rootCmd, s) - if s.AddTestCommand { - noop.Setup(rootCmd, s) - } + // if s.AddTestCommand { + noop.Setup(rootCmd, s) + // } cobra.EnableTraverseRunHooks = true @@ -246,7 +248,7 @@ func (c *Cmd) completeProject( return nil, cobra.ShellCompDirectiveError } - return completions.Projects(ctx, c.Rig, toComplete, c.Scope) + return completions.Projects(ctx, c.Rig, toComplete) } func (c *Cmd) completeEnvironment( @@ -260,7 +262,7 @@ func (c *Cmd) completeEnvironment( return nil, cobra.ShellCompDirectiveError } - return completions.Environments(ctx, c.Rig, toComplete, c.Scope) + return completions.Environments(ctx, c.Rig, toComplete) } func (c *Cmd) completeContext( diff --git a/cmd/rig/cmd/serviceaccount/setup.go b/cmd/rig/cmd/serviceaccount/setup.go index 008820c0e..3652736c5 100644 --- a/cmd/rig/cmd/serviceaccount/setup.go +++ b/cmd/rig/cmd/serviceaccount/setup.go @@ -13,6 +13,7 @@ import ( "github.com/rigdev/rig/cmd/common" "github.com/rigdev/rig/cmd/rig/services/auth" "github.com/rigdev/rig/pkg/cli" + "github.com/rigdev/rig/pkg/cli/scope" "github.com/spf13/cobra" "go.uber.org/fx" ) @@ -31,6 +32,7 @@ type Cmd struct { Rig rig.Client Prompter common.Prompter + Scope scope.Scope } var cmd Cmd @@ -38,6 +40,7 @@ var cmd Cmd func initCmd(c Cmd) { cmd.Rig = c.Rig cmd.Prompter = c.Prompter + cmd.Scope = c.Scope } func Setup(parent *cobra.Command, s *cli.SetupContext) { diff --git a/cmd/rig/cmd/testing/some_test.go b/cmd/rig/cmd/testing/some_test.go index 24efddda7..72c009aef 100644 --- a/cmd/rig/cmd/testing/some_test.go +++ b/cmd/rig/cmd/testing/some_test.go @@ -243,7 +243,7 @@ func newEnv(name string) *environment.Environment { } func (s *testSuite) Test_empty_config_omit_all() { - s.Require().NoError(s.run(true, []string{"noop", "cmd1"})) + s.Require().NoError(s.run(true, []string{"noop", "cmd3"})) } func (s *testSuite) Test_empty_config_omit_none() { @@ -269,10 +269,14 @@ func (s *testSuite) Test_empty_config_omit_none() { } func (s *testSuite) Test_empty_config_non_interactive() { - s.Require().NoError(s.run(false, []string{"noop", "cmd1"})) + s.Require().Error(s.run(false, []string{"noop", "cmd1"})) s.Require().Error(s.run(false, []string{"noop", "cmd2"})) } +func (s *testSuite) Test_empty_config_non_interactive_no_rig() { + s.Require().NoError(s.run(false, []string{"noop", "cmd3"})) +} + func (s *testSuite) Test_has_context_but_none_chosen() { s.saveConfig(&cmdconfig.Config{ Contexts: []*cmdconfig.Context{{ diff --git a/cmd/rig/cmd/user/setup.go b/cmd/rig/cmd/user/setup.go index 9d337849b..c29fffba4 100644 --- a/cmd/rig/cmd/user/setup.go +++ b/cmd/rig/cmd/user/setup.go @@ -13,6 +13,7 @@ import ( "github.com/rigdev/rig/cmd/common" "github.com/rigdev/rig/cmd/rig/services/auth" "github.com/rigdev/rig/pkg/cli" + "github.com/rigdev/rig/pkg/cli/scope" "github.com/spf13/cobra" "go.uber.org/fx" ) @@ -36,6 +37,7 @@ type Cmd struct { Rig rig.Client Prompter common.Prompter + Scope scope.Scope } var cmd Cmd diff --git a/docs/docs/platform/rbac.mdx b/docs/docs/platform/rbac.mdx index e5c319897..9724a7174 100644 --- a/docs/docs/platform/rbac.mdx +++ b/docs/docs/platform/rbac.mdx @@ -51,9 +51,9 @@ The following table shows the permissions for each role: | Create |:heavy_check_mark:|:heavy_check_mark:| | | | Delete |:heavy_check_mark:|:heavy_check_mark:| | | | Delete Images |:heavy_check_mark:|:heavy_check_mark:| | | +| Stop Rollouts |:heavy_check_mark:|:heavy_check_mark:| | | | Add Images |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:| | | Deploy Rollouts |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:| | -| Abort Rollouts |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:| | | Restart Instances |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:| | | Exec in Instances |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:| | | **View Data** |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:| diff --git a/go.mod b/go.mod index 7ca96e68c..04a881ab1 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/rigdev/rig go 1.22 require ( - connectrpc.com/connect v1.16.0 + connectrpc.com/connect v1.16.1 connectrpc.com/grpcreflect v1.2.0 github.com/cert-manager/cert-manager v1.13.1 github.com/distribution/reference v0.5.0 @@ -30,8 +30,8 @@ require ( github.com/nyaruka/phonenumbers v1.1.7 github.com/pkg/errors v0.9.1 github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.70.0 - github.com/rigdev/rig-go-api v0.0.0-20240408063258-c8aa58407458 - github.com/rigdev/rig-go-sdk v0.0.0-20240306122601-f36342e359ef + github.com/rigdev/rig-go-api v0.0.0-20240419130241-0d1e7be1236b + github.com/rigdev/rig-go-sdk v0.0.0-20240417114812-ecae62a90e9b github.com/rivo/tview v0.0.0-20240225120200-5605142ca62e github.com/robfig/cron v1.2.0 github.com/robfig/cron/v3 v3.0.1 @@ -47,7 +47,7 @@ require ( golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 golang.org/x/net v0.24.0 golang.org/x/term v0.19.0 - google.golang.org/grpc v1.63.0 + google.golang.org/grpc v1.63.2 google.golang.org/protobuf v1.33.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 @@ -159,7 +159,7 @@ require ( golang.org/x/tools v0.18.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gotest.tools/v3 v3.5.1 // indirect diff --git a/go.sum b/go.sum index ec5ef3768..0d2f048ad 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -connectrpc.com/connect v1.16.0 h1:rdtfQjZ0OyFkWPTegBNcH7cwquGAN1WzyJy80oFNibg= -connectrpc.com/connect v1.16.0/go.mod h1:XpZAduBQUySsb4/KO5JffORVkDI4B6/EYPi7N8xpNZw= +connectrpc.com/connect v1.16.1 h1:rOdrK/RTI/7TVnn3JsVxt3n028MlTRwmK5Q4heSpjis= +connectrpc.com/connect v1.16.1/go.mod h1:XpZAduBQUySsb4/KO5JffORVkDI4B6/EYPi7N8xpNZw= connectrpc.com/grpcreflect v1.2.0 h1:Q6og1S7HinmtbEuBvARLNwYmTbhEGRpHDhqrPNlmK+U= connectrpc.com/grpcreflect v1.2.0/go.mod h1:nwSOKmE8nU5u/CidgHtPYk1PFI3U9ignz7iDMxOYkSY= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= @@ -274,10 +274,10 @@ github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lne github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/rigdev/rig-go-api v0.0.0-20240408063258-c8aa58407458 h1:Skep/WB6fuccnQLTm6LeFnsbiQAqfZApqf1CEhq6UZ8= -github.com/rigdev/rig-go-api v0.0.0-20240408063258-c8aa58407458/go.mod h1:Awa9vLi0xnEloFj2tYpMBe3VCCerm0Fdh6tT5NC60UM= -github.com/rigdev/rig-go-sdk v0.0.0-20240306122601-f36342e359ef h1:g2VRoPd/fZWmuLZ8c+Nqo1QkpHd6ohMfcKf+1JzpoeQ= -github.com/rigdev/rig-go-sdk v0.0.0-20240306122601-f36342e359ef/go.mod h1:RSzrgNlAsBiUgcSORU0YKw3e97G7GFfQCcM3Sj1pznc= +github.com/rigdev/rig-go-api v0.0.0-20240419130241-0d1e7be1236b h1:PGy7QXThVTINJaDLdS9yzgeIIxOmDCN/xATkOFHNzug= +github.com/rigdev/rig-go-api v0.0.0-20240419130241-0d1e7be1236b/go.mod h1:0GWZXcPZXpGDoB7FhQAj1CWizEIUjELZenmeGpCnuU4= +github.com/rigdev/rig-go-sdk v0.0.0-20240417114812-ecae62a90e9b h1:owpf6cgWMmO31ReecaGvqlmtgj8ozyhxIK1lhTCDUCw= +github.com/rigdev/rig-go-sdk v0.0.0-20240417114812-ecae62a90e9b/go.mod h1:RSzrgNlAsBiUgcSORU0YKw3e97G7GFfQCcM3Sj1pznc= github.com/rivo/tview v0.0.0-20240225120200-5605142ca62e h1:7ubTieBkl4KCz5ABZzh0zPkBYWPguSOHUundUsorIzQ= github.com/rivo/tview v0.0.0-20240225120200-5605142ca62e/go.mod h1:02iFIz7K/A9jGCvrizLPvoqr4cEIx7q54RH5Qudkrss= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -441,10 +441,10 @@ gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= -google.golang.org/grpc v1.63.0 h1:WjKe+dnvABXyPJMD7KDNLxtoGk5tgk+YFWN6cBWjZE8= -google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= diff --git a/pkg/cli/client.go b/pkg/cli/client.go index 96c014a82..94272f83d 100644 --- a/pkg/cli/client.go +++ b/pkg/cli/client.go @@ -8,15 +8,14 @@ import ( "connectrpc.com/connect" "github.com/rigdev/rig-go-sdk" - "github.com/rigdev/rig/cmd/rig/cmd/cmdconfig" "github.com/rigdev/rig/cmd/rig/cmd/flags" "github.com/rigdev/rig/pkg/cli/scope" ) -func GetClientOptions(cfg *cmdconfig.Config) ([]rig.Option, error) { +func GetClientOptions(scope scope.Scope) ([]rig.Option, error) { options := []rig.Option{ rig.WithInterceptors(&userAgentInterceptor{}), - rig.WithSessionManager(&configSessionManager{cfg: cfg}), + rig.WithSessionManager(&configSessionManager{scope: scope}), } if flags.Flags.BasicAuth { @@ -25,7 +24,7 @@ func GetClientOptions(cfg *cmdconfig.Config) ([]rig.Option, error) { host := flags.Flags.Host if host == "" { - if rCtx := cfg.GetCurrentContext(); rCtx != nil { + if rCtx := scope.GetCurrentContext(); rCtx != nil { if svc := rCtx.GetService(); svc != nil { host = svc.Server } @@ -37,7 +36,7 @@ func GetClientOptions(cfg *cmdconfig.Config) ([]rig.Option, error) { } func newRigClient(scope scope.Scope) (rig.Client, error) { - opts, err := GetClientOptions(scope.GetCfg()) + opts, err := GetClientOptions(scope) if err != nil { return nil, err } @@ -74,21 +73,21 @@ func (i *userAgentInterceptor) setUserAgent(h http.Header) { } type configSessionManager struct { - cfg *cmdconfig.Config + scope scope.Scope } func (s *configSessionManager) GetAccessToken() string { - return s.cfg.GetCurrentContext().GetAuth().AccessToken + return s.scope.GetCurrentContext().GetAuth().AccessToken } func (s *configSessionManager) GetRefreshToken() string { - return s.cfg.GetCurrentContext().GetAuth().RefreshToken + return s.scope.GetCurrentContext().GetAuth().RefreshToken } func (s *configSessionManager) SetAccessToken(accessToken, refreshToken string) { - s.cfg.GetCurrentContext().GetAuth().AccessToken = accessToken - s.cfg.GetCurrentContext().GetAuth().RefreshToken = refreshToken - if err := s.cfg.Save(); err != nil { + s.scope.GetCurrentContext().GetAuth().AccessToken = accessToken + s.scope.GetCurrentContext().GetAuth().RefreshToken = refreshToken + if err := s.scope.GetCfg().Save(); err != nil { fmt.Fprintf(os.Stderr, "error saving config: %v\n", err) } } diff --git a/pkg/cli/test_module.go b/pkg/cli/test_module.go index 9c610eff0..48d0996fd 100644 --- a/pkg/cli/test_module.go +++ b/pkg/cli/test_module.go @@ -27,8 +27,8 @@ func MakeTestModule(i TestModuleInput) fx.Option { "test-rig-cli", fx.Provide(func() afero.Fs { return i.FS }), fx.Provide(func() common.Prompter { return i.Prompter }), - fx.Provide(func(cfg *cmdconfig.Config) (rig.Client, error) { - _, err := GetClientOptions(cfg) + fx.Provide(func(scope scope.Scope) (rig.Client, error) { + _, err := GetClientOptions(scope) if err != nil { return nil, err } diff --git a/pkg/rbac/permissions.go b/pkg/rbac/permissions.go index e3c112042..421900884 100644 --- a/pkg/rbac/permissions.go +++ b/pkg/rbac/permissions.go @@ -205,6 +205,14 @@ func GetDeveloperPermissions(projectID, environmentID string) []*role.Permission Project: projectID, }, }, + { + Action: ActionCapsuleDeployAnnotations, + Scope: &role.Scope{ + Resource: WithWildcard(ResourceCapsule), + Environment: environmentID, + Project: projectID, + }, + }, { Action: ActionCapsuleDeployEnvironmentVariables, Scope: &role.Scope{ @@ -257,12 +265,28 @@ func GetDeveloperPermissions(projectID, environmentID string) []*role.Permission } // Owners can do everything a developer can do, plus: -// - Capsule Edit. This means Create, Delete and Update capsules +// - Capsule Create, Delete and Edit. // - Image Delete. This means Delete images // - Capsule Stop Rollout. This means Stop rollouts func GetOwnerPermissions(projectID, environmentID string) []*role.Permission { permissions := GetDeveloperPermissions(projectID, environmentID) return append(permissions, []*role.Permission{ + { + Action: ActionCapsuleCreate, + Scope: &role.Scope{ + Resource: WithWildcard(ResourceCapsule), + Environment: environmentID, + Project: projectID, + }, + }, + { + Action: ActionCapsuleDelete, + Scope: &role.Scope{ + Resource: WithWildcard(ResourceCapsule), + Environment: environmentID, + Project: projectID, + }, + }, { Action: ActionCapsuleEdit, Scope: &role.Scope{