Skip to content

Commit

Permalink
Implement CLI for permissions (#2229)
Browse files Browse the repository at this point in the history
* Implement CLI for permissions

This implements role granting, denying and listing.

* Update cmd/cli/app/project/role/role_list.go

Co-authored-by: Radoslav Dimitrov <[email protected]>

* update comment

* Fix required markings

---------

Co-authored-by: Radoslav Dimitrov <[email protected]>
  • Loading branch information
JAORMX and rdimitrov authored Jan 31, 2024
1 parent 6f291c9 commit e4c3757
Show file tree
Hide file tree
Showing 8 changed files with 345 additions and 0 deletions.
37 changes: 37 additions & 0 deletions cmd/cli/app/project/project.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// Copyright 2024 Stacklok, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package project is the root command for the project subcommands
package project

import (
"github.com/spf13/cobra"

"github.com/stacklok/minder/cmd/cli/app"
)

// ProjectCmd is the root command for the project subcommands
var ProjectCmd = &cobra.Command{
Use: "project",
Short: "Manage project within a minder control plane",
Long: `The minder project commands manage projects within a minder control plane.`,
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Usage()
},
}

func init() {
app.RootCmd.AddCommand(ProjectCmd)
}
38 changes: 38 additions & 0 deletions cmd/cli/app/project/role/role.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// Copyright 2024 Stacklok, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package role is the root command for the role subcommands
package role

import (
"github.com/spf13/cobra"

"github.com/stacklok/minder/cmd/cli/app/project"
)

// RoleCmd is the root command for the project subcommands
var RoleCmd = &cobra.Command{
Use: "role",
Short: "Manage roles within a minder control plane",
Long: `The minder role commands manage permissions within a minder control plane.`,
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Usage()
},
}

func init() {
project.ProjectCmd.AddCommand(RoleCmd)
RoleCmd.PersistentFlags().StringP("project", "j", "", "ID of the project")
}
80 changes: 80 additions & 0 deletions cmd/cli/app/project/role/role_deny.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// Copyright 2024 Stacklok, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package role

import (
"context"
"os"

"github.com/spf13/cobra"
"github.com/spf13/viper"
"google.golang.org/grpc"

"github.com/stacklok/minder/internal/util/cli"
minderv1 "github.com/stacklok/minder/pkg/api/protobuf/go/minder/v1"
)

var denyCmd = &cobra.Command{
Use: "deny",
Short: "Deny a role to a subject on a project within the minder control plane",
Long: `The minder project role deny command removes a user from a role grant
on a particular project.`,
RunE: cli.GRPCClientWrapRunE(DenyCommand),
}

// DenyCommand is the command for removing a role assignment from a project
func DenyCommand(ctx context.Context, cmd *cobra.Command, conn *grpc.ClientConn) error {
client := minderv1.NewPermissionsServiceClient(conn)

sub := viper.GetString("sub")
r := viper.GetString("role")
project := viper.GetString("project")

// No longer print usage on returned error, since we've parsed our inputs
// See https://github.com/spf13/cobra/issues/340#issuecomment-374617413
cmd.SilenceUsage = true

_, err := client.RemoveRole(ctx, &minderv1.RemoveRoleRequest{
Context: &minderv1.Context{
Project: &project,
},
RoleAssignment: &minderv1.RoleAssignment{
Role: r,
Subject: sub,
},
})
if err != nil {
return cli.MessageAndError("Error denying role", err)
}

cmd.Println("Denied role successfully.")
return nil
}

func init() {
RoleCmd.AddCommand(denyCmd)

denyCmd.Flags().StringP("sub", "s", "", "subject to grant access to")
denyCmd.Flags().StringP("role", "r", "", "the role to grant")
if err := denyCmd.MarkFlagRequired("sub"); err != nil {
denyCmd.Print("Error marking `sub` flag as required.")
os.Exit(1)
}
if err := denyCmd.MarkFlagRequired("role"); err != nil {
denyCmd.Print("Error marking `role` flag as required.")
os.Exit(1)
}
}
80 changes: 80 additions & 0 deletions cmd/cli/app/project/role/role_grant.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// Copyright 2024 Stacklok, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package role

import (
"context"
"os"

"github.com/spf13/cobra"
"github.com/spf13/viper"
"google.golang.org/grpc"

"github.com/stacklok/minder/internal/util/cli"
minderv1 "github.com/stacklok/minder/pkg/api/protobuf/go/minder/v1"
)

var grantCmd = &cobra.Command{
Use: "grant",
Short: "Grant a role to a subject on a project within the minder control plane",
Long: `The minder project role grant command allows one to grant a role
to a user (subject) on a particular project.`,
RunE: cli.GRPCClientWrapRunE(GrantCommand),
}

// GrantCommand is the command for granting roles
func GrantCommand(ctx context.Context, cmd *cobra.Command, conn *grpc.ClientConn) error {
client := minderv1.NewPermissionsServiceClient(conn)

sub := viper.GetString("sub")
r := viper.GetString("role")
project := viper.GetString("project")

// No longer print usage on returned error, since we've parsed our inputs
// See https://github.com/spf13/cobra/issues/340#issuecomment-374617413
cmd.SilenceUsage = true

_, err := client.AssignRole(ctx, &minderv1.AssignRoleRequest{
Context: &minderv1.Context{
Project: &project,
},
RoleAssignment: &minderv1.RoleAssignment{
Role: r,
Subject: sub,
},
})
if err != nil {
return cli.MessageAndError("Error granting role", err)
}

cmd.Println("Granted role successfully.")
return nil
}

func init() {
RoleCmd.AddCommand(grantCmd)

grantCmd.Flags().StringP("sub", "s", "", "subject to grant access to")
grantCmd.Flags().StringP("role", "r", "", "the role to grant")
if err := grantCmd.MarkFlagRequired("sub"); err != nil {
grantCmd.Print("Error marking `sub` flag as required.")
os.Exit(1)
}
if err := grantCmd.MarkFlagRequired("role"); err != nil {
grantCmd.Print("Error marking `role` flag as required.")
os.Exit(1)
}
}
98 changes: 98 additions & 0 deletions cmd/cli/app/project/role/role_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//
// Copyright 2024 Stacklok, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package role

import (
"context"
"fmt"
"strings"

"github.com/spf13/cobra"
"github.com/spf13/viper"
"google.golang.org/grpc"

"github.com/stacklok/minder/cmd/cli/app"
"github.com/stacklok/minder/internal/util"
"github.com/stacklok/minder/internal/util/cli"
"github.com/stacklok/minder/internal/util/cli/table"
"github.com/stacklok/minder/internal/util/cli/table/layouts"
minderv1 "github.com/stacklok/minder/pkg/api/protobuf/go/minder/v1"
)

var listCmd = &cobra.Command{
Use: "list",
Short: "List roles on a project within the minder control plane",
Long: `The minder project role list command allows one to list roles
available on a particular project.`,
RunE: cli.GRPCClientWrapRunE(ListCommand),
}

// ListCommand is the command for listing roles
func ListCommand(ctx context.Context, cmd *cobra.Command, conn *grpc.ClientConn) error {
client := minderv1.NewPermissionsServiceClient(conn)

project := viper.GetString("project")
format := viper.GetString("output")
// Ensure the output format is supported
if !app.IsOutputFormatSupported(format) {
return cli.MessageAndError(fmt.Sprintf("Output format %s not supported", format), fmt.Errorf("invalid argument"))
}

// No longer print usage on returned error, since we've parsed our inputs
// See https://github.com/spf13/cobra/issues/340#issuecomment-374617413
cmd.SilenceUsage = true

resp, err := client.ListRoles(ctx, &minderv1.ListRolesRequest{
Context: &minderv1.Context{
Project: &project,
},
})
if err != nil {
return cli.MessageAndError("Error listing roles", err)
}

switch format {
case app.JSON:
out, err := util.GetJsonFromProto(resp)
if err != nil {
return cli.MessageAndError("Error getting json from proto", err)
}
cmd.Println(out)
case app.YAML:
out, err := util.GetYamlFromProto(resp)
if err != nil {
return cli.MessageAndError("Error getting yaml from proto", err)
}
cmd.Println(out)
case app.Table:
t := initializeTableForList()
for _, r := range resp.Roles {
t.AddRow(r.Name, r.Description)
}
t.Render()
}
return nil
}

func initializeTableForList() table.Table {
return table.New(table.Simple, layouts.RoleList, nil)
}

func init() {
RoleCmd.AddCommand(listCmd)
listCmd.Flags().StringP("output", "o", app.Table,
fmt.Sprintf("Output format (one of %s)", strings.Join(app.SupportedOutputFormats(), ",")))
}
2 changes: 2 additions & 0 deletions cmd/cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
_ "github.com/stacklok/minder/cmd/cli/app/docs"
_ "github.com/stacklok/minder/cmd/cli/app/profile"
_ "github.com/stacklok/minder/cmd/cli/app/profile/status"
_ "github.com/stacklok/minder/cmd/cli/app/project"
_ "github.com/stacklok/minder/cmd/cli/app/project/role"
_ "github.com/stacklok/minder/cmd/cli/app/provider"
_ "github.com/stacklok/minder/cmd/cli/app/quickstart"
_ "github.com/stacklok/minder/cmd/cli/app/repo"
Expand Down
2 changes: 2 additions & 0 deletions internal/util/cli/table/layouts/layouts.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ const (
ProfileStatus TableLayout = "profile_status"
// RuleEvaluations is the rule evaluations table layout
RuleEvaluations TableLayout = "rule_evaluations"
// RoleList is the roles list table layout
RoleList TableLayout = "role_list"
// Default is the default table layout
Default TableLayout = ""
)
Expand Down
8 changes: 8 additions & 0 deletions internal/util/cli/table/simple/simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ func New(layout layouts.TableLayout, header []string) *Table {
profileStatusLayout(table)
case layouts.RuleEvaluations:
ruleEvaluationsLayout(table)
case layouts.RoleList:
roleListLayout(table)
case layouts.Default:
table.SetHeader(header)
defaultLayout(table)
Expand Down Expand Up @@ -136,3 +138,9 @@ func ruleTypeLayout(table *tablewriter.Table) {
// This is needed for the rule definition and rule parameters
table.SetAutoWrapText(false)
}

func roleListLayout(table *tablewriter.Table) {
defaultLayout(table)
table.SetHeader([]string{"Name", "Description"})
table.SetAutoWrapText(false)
}

0 comments on commit e4c3757

Please sign in to comment.