From 547339f35f3011bf222f9dbfc9178ec8a1e767a5 Mon Sep 17 00:00:00 2001 From: bupd Date: Sun, 2 Jun 2024 11:35:29 +0530 Subject: [PATCH 1/7] add: robot list command Signed-off-by: bupd --- cmd/harbor/root/project/cmd.go | 1 + cmd/harbor/root/project/robot.go | 19 ++++++ cmd/harbor/root/project/robot/list.go | 64 +++++++++++++++++++ pkg/api/robot_handler.go | 28 ++++++++ pkg/constants/constants.go | 7 ++ pkg/prompt/prompt.go | 10 +++ pkg/views/project/select/view.go | 24 ++++++- pkg/views/robot/list/view.go | 92 +++++++++++++++++++++++++++ pkg/views/styles.go | 1 + 9 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 cmd/harbor/root/project/robot.go create mode 100644 cmd/harbor/root/project/robot/list.go create mode 100644 pkg/api/robot_handler.go create mode 100644 pkg/views/robot/list/view.go diff --git a/cmd/harbor/root/project/cmd.go b/cmd/harbor/root/project/cmd.go index b9987cca..d45df6fb 100644 --- a/cmd/harbor/root/project/cmd.go +++ b/cmd/harbor/root/project/cmd.go @@ -31,6 +31,7 @@ func Project() *cobra.Command { ViewCommand(), LogsProjectCommmand(), SearchProjectCommand(), + Robot(), ) return cmd diff --git a/cmd/harbor/root/project/robot.go b/cmd/harbor/root/project/robot.go new file mode 100644 index 00000000..2dfcfd0f --- /dev/null +++ b/cmd/harbor/root/project/robot.go @@ -0,0 +1,19 @@ +package project + +import ( + "github.com/goharbor/harbor-cli/cmd/harbor/root/project/robot" + "github.com/spf13/cobra" +) + +func Robot() *cobra.Command { + cmd := &cobra.Command{ + Use: "robot", + Short: "Manage robot accounts", + Example: ` harbor robot list`, + } + cmd.AddCommand( + robot.ListRobotCommand(), + ) + + return cmd +} diff --git a/cmd/harbor/root/project/robot/list.go b/cmd/harbor/root/project/robot/list.go new file mode 100644 index 00000000..9730daad --- /dev/null +++ b/cmd/harbor/root/project/robot/list.go @@ -0,0 +1,64 @@ +package robot + +import ( + "log" + "strconv" + + "github.com/goharbor/harbor-cli/pkg/api" + "github.com/goharbor/harbor-cli/pkg/constants" + "github.com/goharbor/harbor-cli/pkg/prompt" + "github.com/goharbor/harbor-cli/pkg/utils" + "github.com/goharbor/harbor-cli/pkg/views/robot/list" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// ListRobotCommand creates a new `harbor robot list` command +func ListRobotCommand() *cobra.Command { + var ( + query string + opts api.ListFlags + ) + + projectQString := constants.ProjectQString + cmd := &cobra.Command{ + Use: "list [projectID]", + Short: "list robot", + Args: cobra.MaximumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + if len(args) > 0 { + opts.Q = projectQString + args[0] + } else { + projectID := prompt.GetProjectIDFromUser() + opts.Q = projectQString + strconv.FormatInt(projectID, 10) + } + + robots, err := api.ListRobot(opts) + if err != nil { + log.Fatalf("failed to get robots list: %v", err) + } + + FormatFlag := viper.GetString("output-format") + if FormatFlag != "" { + utils.PrintPayloadInJSONFormat(robots) + return + } + + list.ListRobots(robots.Payload) + }, + } + + flags := cmd.Flags() + flags.Int64VarP(&opts.Page, "page", "", 1, "Page number") + flags.Int64VarP(&opts.PageSize, "page-size", "", 10, "Size of per page") + flags.StringVarP(&query, "query", "q", "", "Query string to query resources") + flags.StringVarP( + &opts.Sort, + "sort", + "", + "", + "Sort the resource list in ascending or descending order", + ) + + return cmd +} diff --git a/pkg/api/robot_handler.go b/pkg/api/robot_handler.go new file mode 100644 index 00000000..45e5407f --- /dev/null +++ b/pkg/api/robot_handler.go @@ -0,0 +1,28 @@ +package api + +import ( + "github.com/goharbor/go-client/pkg/sdk/v2.0/client/robot" + "github.com/goharbor/harbor-cli/pkg/utils" +) + +func ListRobot(opts ListFlags) (*robot.ListRobotOK, error) { + ctx, client, err := utils.ContextWithClient() + if err != nil { + return nil, err + } + + response, err := client.Robot.ListRobot( + ctx, + &robot.ListRobotParams{ + Page: &opts.Page, + PageSize: &opts.PageSize, + Q: &opts.Q, + Sort: &opts.Sort, + }, + ) + if err != nil { + return nil, err + } + + return response, nil +} diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go index 39557532..ceca02c6 100644 --- a/pkg/constants/constants.go +++ b/pkg/constants/constants.go @@ -12,3 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. package constants + +const ( + HarborCredentialName = "HARBORCREDENTIALNAME" + CredentialNameOption = "credential-name" + CredentialNameHelp = "Name of the credential to use for authentication (defaults to the current logged in session)" + ProjectQString = "Level%3Dproject%2CProjectID%3D" +) diff --git a/pkg/prompt/prompt.go b/pkg/prompt/prompt.go index 10dfc217..431ebd11 100644 --- a/pkg/prompt/prompt.go +++ b/pkg/prompt/prompt.go @@ -45,6 +45,16 @@ func GetProjectNameFromUser() string { return <-projectName } +func GetProjectIDFromUser() int64 { + projectID := make(chan int64) + go func() { + response, _ := api.ListProject() + pview.ProjectListID(response.Payload, projectID) + }() + + return <-projectID +} + func GetRepoNameFromUser(projectName string) string { repositoryName := make(chan string) diff --git a/pkg/views/project/select/view.go b/pkg/views/project/select/view.go index 00053425..31fbdf5b 100644 --- a/pkg/views/project/select/view.go +++ b/pkg/views/project/select/view.go @@ -32,7 +32,6 @@ func ProjectList(project []*models.Project, choice chan<- string) { m := selection.NewModel(items, "Project") p, err := tea.NewProgram(m, tea.WithAltScreen()).Run() - if err != nil { fmt.Println("Error running program:", err) os.Exit(1) @@ -42,3 +41,26 @@ func ProjectList(project []*models.Project, choice chan<- string) { choice <- p.Choice } } + +func ProjectListID(project []*models.Project, choice chan<- int64) { + itemList := make([]list.Item, len(project)) + + items := map[string]int32{} + + for i, p := range project { + itemList[i] = selection.Item(p.Name) + items[p.Name] = p.ProjectID + } + + m := selection.NewModel(itemList, "Project") + + p, err := tea.NewProgram(m, tea.WithAltScreen()).Run() + if err != nil { + fmt.Println("Error running program:", err) + os.Exit(1) + } + + if p, ok := p.(selection.Model); ok { + choice <- int64(items[p.Choice]) + } +} diff --git a/pkg/views/robot/list/view.go b/pkg/views/robot/list/view.go new file mode 100644 index 00000000..8f1af031 --- /dev/null +++ b/pkg/views/robot/list/view.go @@ -0,0 +1,92 @@ +package list + +import ( + "fmt" + "log" + "os" + "strconv" + "time" + + "github.com/charmbracelet/bubbles/table" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" + "github.com/goharbor/harbor-cli/pkg/utils" + "github.com/goharbor/harbor-cli/pkg/views" + "github.com/goharbor/harbor-cli/pkg/views/base/tablelist" +) + +var columns = []table.Column{ + {Title: "Name", Width: 30}, + {Title: "Status", Width: 18}, + {Title: "Permissions", Width: 12}, + {Title: "Creation Time", Width: 16}, + {Title: "Expires in", Width: 14}, + {Title: "Description", Width: 12}, +} + +func ListRobots(robots []*models.Robot) { + var rows []table.Row + + for _, robot := range robots { + var enabledStatus string + var expires string + + if robot.Disable { + enabledStatus = views.RedStyle.Render("Disabled") + } else { + enabledStatus = views.GreenStyle.Render("Enabled") + } + + TotalPermissions := strconv.FormatInt(int64(len(robot.Permissions)), 10) + + if robot.ExpiresAt == -1 { + expires = "Never" + } else { + expires = remainingTime(robot.ExpiresAt) + } + + log.Println(expires) + createdTime, _ := utils.FormatCreatedTime(robot.CreationTime.String()) + rows = append(rows, table.Row{ + robot.Name, // Project Name + enabledStatus, // Access Level + TotalPermissions, + createdTime, // Creation Time + expires, + robot.Description, + }) + } + + m := tablelist.NewModel(columns, rows, len(rows)) + + if _, err := tea.NewProgram(m).Run(); err != nil { + fmt.Println("Error running program:", err) + os.Exit(1) + } +} + +func remainingTime(unixTimestamp int64) string { + // Get the current time + now := time.Now() + // Convert the Unix timestamp to time.Time + expirationTime := time.Unix(unixTimestamp, 0) + // Calculate the duration between now and the expiration time + duration := expirationTime.Sub(now) + + // Calculate days, hours, minutes, and seconds + days := int(duration.Hours() / 24) + hours := int(duration.Hours()) % 24 + minutes := int(duration.Minutes()) % 60 + + // Format the output string + return fmt.Sprintf("%dd %dh %dm", days, hours, minutes) +} + +func getStatusStyle(status string) lipgloss.Style { + statusStyle := views.RedStyle + if status == "healthy" { + statusStyle = views.GreenStyle + } + return statusStyle +} diff --git a/pkg/views/styles.go b/pkg/views/styles.go index 1f0ab7b5..e86dfc4f 100644 --- a/pkg/views/styles.go +++ b/pkg/views/styles.go @@ -26,6 +26,7 @@ var ( HelpStyle = list.DefaultStyles().HelpStyle.PaddingLeft(4).PaddingBottom(1) RedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF0000")) GreenStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#04B575")) + WhiteStyle = list.DefaultStyles() ) var BaseStyle = lipgloss.NewStyle(). From 7b0ddec166a116a0eb092046ee758b02cb26ce2e Mon Sep 17 00:00:00 2001 From: bupd Date: Mon, 3 Jun 2024 04:26:08 +0530 Subject: [PATCH 2/7] add: delete & get robot commands This commit adds delete and get project robot accounts cmds Signed-off-by: bupd --- cmd/harbor/root/project/robot.go | 2 ++ cmd/harbor/root/project/robot/delete.go | 33 ++++++++++++++++++++ cmd/harbor/root/project/robot/view.go | 40 +++++++++++++++++++++++++ pkg/api/robot_handler.go | 29 ++++++++++++++++++ pkg/views/robot/list/view.go | 2 -- 5 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 cmd/harbor/root/project/robot/delete.go create mode 100644 cmd/harbor/root/project/robot/view.go diff --git a/cmd/harbor/root/project/robot.go b/cmd/harbor/root/project/robot.go index 2dfcfd0f..f13f6f0a 100644 --- a/cmd/harbor/root/project/robot.go +++ b/cmd/harbor/root/project/robot.go @@ -13,6 +13,8 @@ func Robot() *cobra.Command { } cmd.AddCommand( robot.ListRobotCommand(), + robot.DeleteCommand(), + robot.ViewCommand(), ) return cmd diff --git a/cmd/harbor/root/project/robot/delete.go b/cmd/harbor/root/project/robot/delete.go new file mode 100644 index 00000000..105b9196 --- /dev/null +++ b/cmd/harbor/root/project/robot/delete.go @@ -0,0 +1,33 @@ +package robot + +import ( + "strconv" + + "github.com/goharbor/harbor-cli/pkg/api" + log "github.com/sirupsen/logrus" + + "github.com/spf13/cobra" +) + +// NewGetRegistryCommand creates a new `harbor get registry` command +func DeleteCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "delete [robotID]", + Short: "delete robot by id", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 1 { + robotID, err := strconv.ParseInt(args[0], 10, 64) + if err != nil { + log.Errorf("failed to parse robot ID: %v", err) + } + err = api.DeleteRobot(robotID) + if err != nil { + log.Errorf("failed to Delete robots") + } + } + }, + } + + return cmd +} diff --git a/cmd/harbor/root/project/robot/view.go b/cmd/harbor/root/project/robot/view.go new file mode 100644 index 00000000..bd38e5ed --- /dev/null +++ b/cmd/harbor/root/project/robot/view.go @@ -0,0 +1,40 @@ +package robot + +import ( + "strconv" + + "github.com/goharbor/go-client/pkg/sdk/v2.0/client/robot" + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" + "github.com/goharbor/harbor-cli/pkg/api" + "github.com/goharbor/harbor-cli/pkg/views/robot/list" + log "github.com/sirupsen/logrus" + + "github.com/spf13/cobra" +) + +// ViewCommand creates a new `harbor project robot view` command +func ViewCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "view [robotID]", + Short: "get robot by id", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + var robot *robot.GetRobotByIDOK + + if len(args) == 1 { + robotID, err := strconv.ParseInt(args[0], 10, 64) + if err != nil { + log.Errorf("failed to parse robot ID: %v", err) + } + robot, err = api.GetRobot(robotID) + if err != nil { + log.Errorf("failed to List robots") + } + } + robots := []*models.Robot{robot.Payload} + list.ListRobots(robots) + }, + } + + return cmd +} diff --git a/pkg/api/robot_handler.go b/pkg/api/robot_handler.go index 45e5407f..a115cc0d 100644 --- a/pkg/api/robot_handler.go +++ b/pkg/api/robot_handler.go @@ -3,6 +3,7 @@ package api import ( "github.com/goharbor/go-client/pkg/sdk/v2.0/client/robot" "github.com/goharbor/harbor-cli/pkg/utils" + log "github.com/sirupsen/logrus" ) func ListRobot(opts ListFlags) (*robot.ListRobotOK, error) { @@ -26,3 +27,31 @@ func ListRobot(opts ListFlags) (*robot.ListRobotOK, error) { return response, nil } + +func GetRobot(robotID int64) (*robot.GetRobotByIDOK, error) { + ctx, client, err := utils.ContextWithClient() + if err != nil { + return nil, err + } + response, err := client.Robot.GetRobotByID(ctx, &robot.GetRobotByIDParams{RobotID: robotID}) + if err != nil { + return nil, err + } + + return response, nil +} + +func DeleteRobot(robotID int64) error { + ctx, client, err := utils.ContextWithClient() + if err != nil { + return err + } + _, err = client.Robot.DeleteRobot(ctx, &robot.DeleteRobotParams{RobotID: robotID}) + if err != nil { + return err + } + + log.Info("robot deleted successfully") + + return nil +} diff --git a/pkg/views/robot/list/view.go b/pkg/views/robot/list/view.go index 8f1af031..5a1d39bf 100644 --- a/pkg/views/robot/list/view.go +++ b/pkg/views/robot/list/view.go @@ -2,7 +2,6 @@ package list import ( "fmt" - "log" "os" "strconv" "time" @@ -46,7 +45,6 @@ func ListRobots(robots []*models.Robot) { expires = remainingTime(robot.ExpiresAt) } - log.Println(expires) createdTime, _ := utils.FormatCreatedTime(robot.CreationTime.String()) rows = append(rows, table.Row{ robot.Name, // Project Name From 584ef1a979d341a30a0a8696519839544c508697 Mon Sep 17 00:00:00 2001 From: bupd Date: Tue, 4 Jun 2024 09:02:46 +0530 Subject: [PATCH 3/7] add: project robot create command with interactive select permission Signed-off-by: bupd --- cmd/harbor/root/project/robot.go | 1 + cmd/harbor/root/project/robot/create.go | 113 ++++++++++++++ pkg/api/robot_handler.go | 60 ++++++++ pkg/prompt/prompt.go | 11 ++ pkg/views/base/multiselect/model.go | 196 ++++++++++++++++++++++++ pkg/views/robot/create/view.go | 98 ++++++++++++ pkg/views/robot/select/view.go | 30 ++++ 7 files changed, 509 insertions(+) create mode 100644 cmd/harbor/root/project/robot/create.go create mode 100644 pkg/views/base/multiselect/model.go create mode 100644 pkg/views/robot/create/view.go create mode 100644 pkg/views/robot/select/view.go diff --git a/cmd/harbor/root/project/robot.go b/cmd/harbor/root/project/robot.go index f13f6f0a..49d1d189 100644 --- a/cmd/harbor/root/project/robot.go +++ b/cmd/harbor/root/project/robot.go @@ -15,6 +15,7 @@ func Robot() *cobra.Command { robot.ListRobotCommand(), robot.DeleteCommand(), robot.ViewCommand(), + robot.CreateRobot(), ) return cmd diff --git a/cmd/harbor/root/project/robot/create.go b/cmd/harbor/root/project/robot/create.go new file mode 100644 index 00000000..3e24d2d3 --- /dev/null +++ b/cmd/harbor/root/project/robot/create.go @@ -0,0 +1,113 @@ +package robot + +import ( + "os" + + "github.com/atotto/clipboard" + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" + "github.com/goharbor/harbor-cli/pkg/api" + "github.com/goharbor/harbor-cli/pkg/prompt" + "github.com/goharbor/harbor-cli/pkg/utils" + "github.com/goharbor/harbor-cli/pkg/views/robot/create" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// CreateProjectCommand creates a new `harbor create project` command +func CreateRobot() *cobra.Command { + var ( + opts create.CreateView + projectName string + all bool + ) + + cmd := &cobra.Command{ + Use: "create", + Short: "create robot", + Args: cobra.MaximumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + var err error + kind := "project" + opts.Level = kind + + if opts.ProjectName == "" { + opts.ProjectName = prompt.GetProjectNameFromUser() + if opts.ProjectName == "" { + os.Exit(1) + } + } + + // to-do handle permission as json submission + perm := &create.RobotPermission{ + Kind: kind, + Namespace: projectName, + } + opts.Permissions = []*create.RobotPermission{perm} + + if len(args) == 0 { + if opts.Name == "" || opts.Duration == 0 { + create.CreateRobotView(&opts) + } + permissions := []models.Permission{} + + if all { + perms, _ := api.GetPermissions() + permission := perms.Payload.Project + + choices := []models.Permission{} + for _, perm := range permission { + choices = append(choices, *perm) + } + permissions = choices + } else { + permissions = prompt.GetRobotPermissionsFromUser() + } + + // []Permission to []*Access + var accesses []*models.Access + for _, perm := range permissions { + access := &models.Access{ + Action: perm.Action, + Resource: perm.Resource, + } + accesses = append(accesses, access) + } + // convert []models.permission to []*model.Access + perm := &create.RobotPermission{ + Kind: kind, + Namespace: projectName, + Access: accesses, + } + opts.Permissions = []*create.RobotPermission{perm} + } + response, err := api.CreateRobot(opts) + if err != nil { + log.Errorf("failed to create robot: %v", err) + } + + FormatFlag := viper.GetString("output-format") + if FormatFlag != "" { + utils.PrintPayloadInJSONFormat(response.Payload) + return + } + + create.CreateRobotSecretView(response.Payload) + err = clipboard.WriteAll(response.Payload.Secret) + }, + } + flags := cmd.Flags() + flags.BoolVarP( + &all, + "all-permission", + "a", + false, + "Select all permissions for the robot account", + ) + flags.StringVarP(&opts.ProjectName, "project", "", "", "set project name") + flags.StringVarP(&opts.Name, "name", "", "", "name of the robot account") + flags.StringVarP(&opts.Description, "description", "", "", "description of the robot account") + flags.Int64VarP(&opts.Duration, "duration", "", 0, "set expiration of robot account in days") + + return cmd +} diff --git a/pkg/api/robot_handler.go b/pkg/api/robot_handler.go index a115cc0d..89bff9e5 100644 --- a/pkg/api/robot_handler.go +++ b/pkg/api/robot_handler.go @@ -1,8 +1,11 @@ package api import ( + "github.com/goharbor/go-client/pkg/sdk/v2.0/client/permissions" "github.com/goharbor/go-client/pkg/sdk/v2.0/client/robot" + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" "github.com/goharbor/harbor-cli/pkg/utils" + "github.com/goharbor/harbor-cli/pkg/views/robot/create" log "github.com/sirupsen/logrus" ) @@ -55,3 +58,60 @@ func DeleteRobot(robotID int64) error { return nil } + +func CreateRobot(opts create.CreateView) (*robot.CreateRobotCreated ,error) { + ctx, client, err := utils.ContextWithClient() + if err != nil { + return nil, err + } + + // Create a slice to store converted permissions + permissions := opts.Permissions + convertedPerms := make([]*models.RobotPermission, 0, len(permissions)) + + project := "project" + // Loop through original permissions and convert them + for _, perm := range permissions { + convertedPerm := &models.RobotPermission{ + Access: perm.Access, + Kind: project, + Namespace: opts.ProjectName, + } + convertedPerms = append(convertedPerms, convertedPerm) + } + response, err := client.Robot.CreateRobot( + ctx, + &robot.CreateRobotParams{ + Robot: &models.RobotCreate{ + Description: opts.Description, + Disable: false, + Duration: opts.Duration, + Level: opts.Level, + Name: opts.Name, + Permissions: convertedPerms, + }, + }, + ) + if err != nil { + return nil,err + } + + log.Info("robot created successfully.") + return response, nil +} + +func GetPermissions() (*permissions.GetPermissionsOK, error) { + ctx, client, err := utils.ContextWithClient() + if err != nil { + return nil, err + } + response, err := client.Permissions.GetPermissions( + ctx, + &permissions.GetPermissionsParams{}, + ) + if err != nil { + return nil, err + } + + return response, nil +} diff --git a/pkg/prompt/prompt.go b/pkg/prompt/prompt.go index 431ebd11..21c4445d 100644 --- a/pkg/prompt/prompt.go +++ b/pkg/prompt/prompt.go @@ -14,6 +14,7 @@ package prompt import ( + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" "github.com/goharbor/harbor-cli/pkg/api" aview "github.com/goharbor/harbor-cli/pkg/views/artifact/select" tview "github.com/goharbor/harbor-cli/pkg/views/artifact/tags/select" @@ -21,6 +22,7 @@ import ( pview "github.com/goharbor/harbor-cli/pkg/views/project/select" rview "github.com/goharbor/harbor-cli/pkg/views/registry/select" repoView "github.com/goharbor/harbor-cli/pkg/views/repository/select" + robotView "github.com/goharbor/harbor-cli/pkg/views/robot/select" uview "github.com/goharbor/harbor-cli/pkg/views/user/select" log "github.com/sirupsen/logrus" ) @@ -117,3 +119,12 @@ func GetLabelIdFromUser(opts api.ListFlags) int64 { return <-labelId } + +func GetRobotPermissionsFromUser() []models.Permission { + permissions := make(chan []models.Permission) + go func() { + response, _ := api.GetPermissions() + robotView.ListPermissions(response.Payload, permissions) + }() + return <-permissions +} diff --git a/pkg/views/base/multiselect/model.go b/pkg/views/base/multiselect/model.go new file mode 100644 index 00000000..b6396a7b --- /dev/null +++ b/pkg/views/base/multiselect/model.go @@ -0,0 +1,196 @@ +package multiselect + +import ( + "fmt" + "strings" + + "github.com/charmbracelet/bubbles/viewport" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" +) + +const useHighPerformanceRenderer = false + +var ( + titleStyle = func() lipgloss.Style { + b := lipgloss.RoundedBorder() + b.Right = "├" + return lipgloss.NewStyle().BorderStyle(b).Padding(0, 1) + }() + + selectedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("43")) + itemStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("46")) + blockStyle = lipgloss.NewStyle(). + Background(lipgloss.Color("81")). + Foreground(lipgloss.Color("#000000")). + Bold(true). + Padding(0, 1, 0) + + infoStyle = func() lipgloss.Style { + b := lipgloss.RoundedBorder() + b.Left = "┤" + return titleStyle.BorderStyle(b) + }() +) + +type Model struct { + content string + ready bool + viewport viewport.Model + choices []models.Permission // items on the to-do list + cursor int // which to-do list item our cursor is pointing at + selected map[int]struct{} // which to-do items are selected + selects *[]models.Permission +} + +func (m Model) Init() tea.Cmd { + return nil +} + +func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var ( + cmd tea.Cmd + cmds []tea.Cmd + ) + + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "ctrl+c", "q", "esc": + return m, tea.Quit + case "y": + m.GetSelectedPermissions() + return m, tea.Quit + case "up", "k": + if m.cursor > 0 { + m.cursor-- + } + case "down", "j": + if m.cursor < len(m.choices)-1 { + m.cursor++ + } + case "enter", " ": + _, ok := m.selected[m.cursor] + if ok { + delete(m.selected, m.cursor) + } else { + m.selected[m.cursor] = struct{}{} + } + } + + case tea.WindowSizeMsg: + headerHeight := lipgloss.Height(m.headerView()) + footerHeight := lipgloss.Height(m.footerView()) + verticalMarginHeight := headerHeight + footerHeight + + if !m.ready { + m.viewport = viewport.New(msg.Width, msg.Height-verticalMarginHeight) + m.viewport.YPosition = headerHeight + m.viewport.HighPerformanceRendering = useHighPerformanceRenderer + m.viewport.SetContent(m.listView()) + m.ready = true + m.viewport.YPosition = headerHeight - 1 + } else { + m.viewport.Width = msg.Width + m.viewport.Height = msg.Height - verticalMarginHeight - 1 + } + + if useHighPerformanceRenderer { + cmds = append(cmds, viewport.Sync(m.viewport)) + } + } + + m.viewport.SetContent(m.listView()) + m.viewport, cmd = m.viewport.Update(msg) + cmds = append(cmds, cmd) + + return m, tea.Batch(cmds...) +} + +func (m Model) View() string { + if !m.ready { + return "\n Initializing..." + } + return fmt.Sprintf("%s\n%s\n%s", m.headerView(), m.viewport.View(), m.footerView()) +} + +func (m Model) headerView() string { + title := titleStyle.Render("Select Permissions for Robot Account") + line := strings.Repeat("─", max(0, m.viewport.Width-lipgloss.Width(title))) + return lipgloss.JoinHorizontal(lipgloss.Center, title, line) +} + +func (m Model) footerView() string { + help := lipgloss.NewStyle().Foreground(lipgloss.Color("238")).Render( + fmt.Sprint( + " up/down: navigate • ", "enter: select permissions • ", "q: quit • ", " y: confirm\t", + ), + ) + info := infoStyle.Render(fmt.Sprintf("%3.f%%", m.viewport.ScrollPercent()*100)) + line := strings.Repeat("─", max(0, m.viewport.Width-lipgloss.Width(info)-lipgloss.Width(help))) + return lipgloss.JoinHorizontal(lipgloss.Center, help, line, info) +} + +func (m Model) listView() string { + s := "Select Robot Permissions\n\n" + var prev string + for i, choice := range m.choices { + // Render the row ith appropriate action message + choiceRes := choice.Resource + choiceAct := choice.Action + now := choice.Resource + if prev != now { + prev = now + s += blockStyle.Render(prev) + s += "\n\n" + } + cursor := " " // no cursor + if m.cursor == i { + choiceRes = itemStyle.Render(choice.Resource) + choiceAct = itemStyle.Render(choice.Action) + cursor = ">" // cursor! + } + checked := " " // not selected + if _, ok := m.selected[i]; ok { + choiceRes = selectedStyle.Render(choice.Resource) + choiceAct = selectedStyle.Render(choice.Action) + checked = "x" // selected! + } + s += fmt.Sprintf( + "%s [%s] %s %s\n\n", + cursor, + checked, + choiceAct, + choiceRes, + ) + } + s += "\nPress q to quit.\n" + + return s +} + + +func (m Model) GetSelectedPermissions() *[]models.Permission { + selectedPermissions := make([]models.Permission, 0, len(m.selected)) + for index := range m.selected { + selectedPermissions = append(selectedPermissions, m.choices[index]) + } + *m.selects = selectedPermissions + return m.selects +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} + +func NewModel(choices []models.Permission, selects *[]models.Permission) Model { + return Model{ + choices: choices, + selected: make(map[int]struct{}), + selects: selects, + } +} diff --git a/pkg/views/robot/create/view.go b/pkg/views/robot/create/view.go new file mode 100644 index 00000000..bb8404a6 --- /dev/null +++ b/pkg/views/robot/create/view.go @@ -0,0 +1,98 @@ +package create + +import ( + "errors" + "strconv" + + "github.com/charmbracelet/huh" + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" + log "github.com/sirupsen/logrus" +) + +type CreateView struct { + Description string `json:"description,omitempty"` + Disable bool `json:"disable,omitempty"` + Duration int64 `json:"duration,omitempty"` + Level string `json:"level,omitempty"` + Name string `json:"name,omitempty"` + Permissions []*RobotPermission `json:"permissions"` + Secret string `json:"secret,omitempty"` + ProjectName string +} + +type RobotPermission struct { + Access []*models.Access `json:"access"` + Kind string `json:"kind,omitempty"` + Namespace string `json:"namespace,omitempty"` +} + +type Access struct { + Action string `json:"action,omitempty"` + Effect string `json:"effect,omitempty"` + Resource string `json:"resource,omitempty"` +} + +func CreateRobotView(createView *CreateView) { + var duration string + duration = strconv.FormatInt(createView.Duration, 10) + + theme := huh.ThemeCharm() + err := huh.NewForm( + huh.NewGroup( + huh.NewInput(). + Title("Name"). + Value(&createView.Name). + Validate(func(str string) error { + if str == "" { + return errors.New("Name cannot be empty") + } + return nil + }), + huh.NewInput(). + Title("Description"). + Value(&createView.Description), + huh.NewInput(). + Title("Expiration"). + Value(&duration). + Validate(func(str string) error { + if str == "" { + return errors.New("Expiration cannot be empty") + } + dur, err := strconv.ParseInt(str, 10, 64) + if err != nil { + return errors.New("invalid expiration time: Enter expiration time in days") + } + createView.Duration = dur + return nil + }), + ), + ).WithTheme(theme).Run() + if err != nil { + log.Fatal(err) + } +} + +func CreateRobotSecretView(response *models.RobotCreated) { + name := response.Name + secret := response.Secret + theme := huh.ThemeCharm() + err := huh.NewForm( + huh.NewGroup( + huh.NewInput(). + Title("Robot Name"). + Value(&name). + Validate(func(str string) error { + if str == "" { + return errors.New("Name cannot be empty") + } + return nil + }), + huh.NewInput(). + Title("Secret"). + Value(&secret), + ), + ).WithTheme(theme).Run() + if err != nil { + log.Fatal(err) + } +} diff --git a/pkg/views/robot/select/view.go b/pkg/views/robot/select/view.go new file mode 100644 index 00000000..cc76dceb --- /dev/null +++ b/pkg/views/robot/select/view.go @@ -0,0 +1,30 @@ +package robot + +import ( + "fmt" + + tea "github.com/charmbracelet/bubbletea" + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" + "github.com/goharbor/harbor-cli/pkg/views/base/multiselect" +) + +func ListPermissions(perms *models.Permissions, ch chan<- []models.Permission) { + permissions := perms.Project + choices := []models.Permission{} + + // Iterate over permissions and append each item to choices + for _, perm := range permissions { + choices = append(choices, *perm) + } + + selects := &[]models.Permission{} + + m := multiselect.NewModel(choices, selects) + + _, err := tea.NewProgram(m).Run() + if err != nil { + fmt.Println("Error running program:", err) + } + // Get selected permissions + ch <- *selects +} From f28f34563dc7e7f07aa7cc25635515f2d747a95b Mon Sep 17 00:00:00 2001 From: bupd Date: Tue, 4 Jun 2024 18:57:24 +0530 Subject: [PATCH 4/7] add: update project robot account cmd Signed-off-by: bupd --- cmd/harbor/root/project/robot.go | 7 +- cmd/harbor/root/project/robot/create.go | 4 +- cmd/harbor/root/project/robot/delete.go | 6 +- cmd/harbor/root/project/robot/list.go | 8 +- cmd/harbor/root/project/robot/update.go | 117 ++++++++++++++++++++++++ cmd/harbor/root/project/robot/view.go | 6 +- pkg/api/robot_handler.go | 56 +++++++++++- pkg/prompt/prompt.go | 15 +++ pkg/views/base/multiselect/model.go | 9 +- pkg/views/robot/list/view.go | 8 +- pkg/views/robot/select/view.go | 29 +++++- pkg/views/robot/update/view.go | 74 +++++++++++++++ 12 files changed, 314 insertions(+), 25 deletions(-) create mode 100644 cmd/harbor/root/project/robot/update.go create mode 100644 pkg/views/robot/update/view.go diff --git a/cmd/harbor/root/project/robot.go b/cmd/harbor/root/project/robot.go index 49d1d189..12a8a619 100644 --- a/cmd/harbor/root/project/robot.go +++ b/cmd/harbor/root/project/robot.go @@ -13,9 +13,10 @@ func Robot() *cobra.Command { } cmd.AddCommand( robot.ListRobotCommand(), - robot.DeleteCommand(), - robot.ViewCommand(), - robot.CreateRobot(), + robot.DeleteRobotCommand(), + robot.ViewRobotCommand(), + robot.CreateRobotCommand(), + robot.UpdateRobotCommand(), ) return cmd diff --git a/cmd/harbor/root/project/robot/create.go b/cmd/harbor/root/project/robot/create.go index 3e24d2d3..87b6f9a0 100644 --- a/cmd/harbor/root/project/robot/create.go +++ b/cmd/harbor/root/project/robot/create.go @@ -14,8 +14,8 @@ import ( "github.com/spf13/viper" ) -// CreateProjectCommand creates a new `harbor create project` command -func CreateRobot() *cobra.Command { +// to-do add json file as input and getting json file as output from input. +func CreateRobotCommand() *cobra.Command { var ( opts create.CreateView projectName string diff --git a/cmd/harbor/root/project/robot/delete.go b/cmd/harbor/root/project/robot/delete.go index 105b9196..09015bdf 100644 --- a/cmd/harbor/root/project/robot/delete.go +++ b/cmd/harbor/root/project/robot/delete.go @@ -9,12 +9,12 @@ import ( "github.com/spf13/cobra" ) -// NewGetRegistryCommand creates a new `harbor get registry` command -func DeleteCommand() *cobra.Command { +// to-do improve DeleteRobotCommand and multi delete +func DeleteRobotCommand() *cobra.Command { cmd := &cobra.Command{ Use: "delete [robotID]", Short: "delete robot by id", - Args: cobra.ExactArgs(1), + Args: cobra.MaximumNArgs(1), Run: func(cmd *cobra.Command, args []string) { if len(args) == 1 { robotID, err := strconv.ParseInt(args[0], 10, 64) diff --git a/cmd/harbor/root/project/robot/list.go b/cmd/harbor/root/project/robot/list.go index 9730daad..2afe9c38 100644 --- a/cmd/harbor/root/project/robot/list.go +++ b/cmd/harbor/root/project/robot/list.go @@ -13,11 +13,11 @@ import ( "github.com/spf13/viper" ) -// ListRobotCommand creates a new `harbor robot list` command +// ListRobotCommand creates a new `harbor project robot list` command func ListRobotCommand() *cobra.Command { var ( - query string - opts api.ListFlags + query string + opts api.ListFlags ) projectQString := constants.ProjectQString @@ -29,7 +29,7 @@ func ListRobotCommand() *cobra.Command { if len(args) > 0 { opts.Q = projectQString + args[0] } else { - projectID := prompt.GetProjectIDFromUser() + projectID := prompt.GetProjectIDFromUser() opts.Q = projectQString + strconv.FormatInt(projectID, 10) } diff --git a/cmd/harbor/root/project/robot/update.go b/cmd/harbor/root/project/robot/update.go new file mode 100644 index 00000000..53cb1ce5 --- /dev/null +++ b/cmd/harbor/root/project/robot/update.go @@ -0,0 +1,117 @@ +package robot + +import ( + "strconv" + + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" + "github.com/goharbor/harbor-cli/pkg/api" + "github.com/goharbor/harbor-cli/pkg/prompt" + "github.com/goharbor/harbor-cli/pkg/views/robot/update" + log "github.com/sirupsen/logrus" + + "github.com/spf13/cobra" +) + +// to-do complete UpdateRobotCommand +func UpdateRobotCommand() *cobra.Command { + var ( + robotID int64 + opts update.UpdateView + all bool + ) + + cmd := &cobra.Command{ + Use: "update [robotID]", + Short: "update robot by id", + Args: cobra.MaximumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + var err error + if len(args) == 1 { + robotID, err = strconv.ParseInt(args[0], 10, 64) + if err != nil { + log.Errorf("failed to parse robot ID: %v", err) + } + + } else { + projectID := prompt.GetProjectIDFromUser() + robotID = prompt.GetRobotIDFromUser(projectID) + } + + robot, err := api.GetRobot(robotID) + bot := robot.Payload + + opts = update.UpdateView{ + CreationTime: bot.CreationTime, + Description: bot.Description, + Disable: bot.Disable, + Duration: bot.Duration, + Editable: bot.Editable, + ID: bot.ID, + Level: bot.Level, + Name: bot.Name, + Secret: bot.Secret, + } + + // declare empty permissions to hold permissions + permissions := []models.Permission{} + + if all { + perms, _ := api.GetPermissions() + permission := perms.Payload.Project + + choices := []models.Permission{} + for _, perm := range permission { + choices = append(choices, *perm) + } + permissions = choices + } else { + permissions = prompt.GetRobotPermissionsFromUser() + } + + // []Permission to []*Access + var accesses []*models.Access + for _, perm := range permissions { + access := &models.Access{ + Action: perm.Action, + Resource: perm.Resource, + } + accesses = append(accesses, access) + } + // convert []models.permission to []*model.Access + perm := &update.RobotPermission{ + Kind: bot.Permissions[0].Kind, + Namespace: bot.Permissions[0].Namespace, + Access: accesses, + } + opts.Permissions = []*update.RobotPermission{perm} + + err = updateRobotView(&opts) + if err != nil { + log.Errorf("failed to Update robot") + } + }, + } + + flags := cmd.Flags() + flags.BoolVarP( + &all, + "all-permission", + "a", + false, + "Select all permissions for the robot account", + ) + flags.StringVarP(&opts.Name, "name", "", "", "name of the robot account") + flags.StringVarP(&opts.Description, "description", "", "", "description of the robot account") + flags.Int64VarP(&opts.Duration, "duration", "", 0, "set expiration of robot account in days") + + return cmd +} + +func updateRobotView(updateView *update.UpdateView) error { + if updateView == nil { + updateView = &update.UpdateView{} + } + + update.UpdateRobotView(updateView) + return api.UpdateRobot(updateView) +} diff --git a/cmd/harbor/root/project/robot/view.go b/cmd/harbor/root/project/robot/view.go index bd38e5ed..e138253a 100644 --- a/cmd/harbor/root/project/robot/view.go +++ b/cmd/harbor/root/project/robot/view.go @@ -1,6 +1,7 @@ package robot import ( + "os" "strconv" "github.com/goharbor/go-client/pkg/sdk/v2.0/client/robot" @@ -12,8 +13,8 @@ import ( "github.com/spf13/cobra" ) -// ViewCommand creates a new `harbor project robot view` command -func ViewCommand() *cobra.Command { +// handle robot view with interactive like in list command. +func ViewRobotCommand() *cobra.Command { cmd := &cobra.Command{ Use: "view [robotID]", Short: "get robot by id", @@ -29,6 +30,7 @@ func ViewCommand() *cobra.Command { robot, err = api.GetRobot(robotID) if err != nil { log.Errorf("failed to List robots") + os.Exit(1) } } robots := []*models.Robot{robot.Payload} diff --git a/pkg/api/robot_handler.go b/pkg/api/robot_handler.go index 89bff9e5..669bbe01 100644 --- a/pkg/api/robot_handler.go +++ b/pkg/api/robot_handler.go @@ -6,6 +6,7 @@ import ( "github.com/goharbor/go-client/pkg/sdk/v2.0/models" "github.com/goharbor/harbor-cli/pkg/utils" "github.com/goharbor/harbor-cli/pkg/views/robot/create" + "github.com/goharbor/harbor-cli/pkg/views/robot/update" log "github.com/sirupsen/logrus" ) @@ -59,7 +60,7 @@ func DeleteRobot(robotID int64) error { return nil } -func CreateRobot(opts create.CreateView) (*robot.CreateRobotCreated ,error) { +func CreateRobot(opts create.CreateView) (*robot.CreateRobotCreated, error) { ctx, client, err := utils.ContextWithClient() if err != nil { return nil, err @@ -79,7 +80,7 @@ func CreateRobot(opts create.CreateView) (*robot.CreateRobotCreated ,error) { } convertedPerms = append(convertedPerms, convertedPerm) } - response, err := client.Robot.CreateRobot( + response, err := client.Robot.CreateRobot( ctx, &robot.CreateRobotParams{ Robot: &models.RobotCreate{ @@ -93,13 +94,62 @@ func CreateRobot(opts create.CreateView) (*robot.CreateRobotCreated ,error) { }, ) if err != nil { - return nil,err + return nil, err } log.Info("robot created successfully.") return response, nil } +// update robot with robotID +func UpdateRobot(opts *update.UpdateView) error { + ctx, client, err := utils.ContextWithClient() + if err != nil { + log.Errorf("Error: %v", err) + return err + } + + log.Println(opts) + + // Create a slice to store converted permissions + permissions := opts.Permissions + convertedPerms := make([]*models.RobotPermission, 0, len(permissions)) + + kind := "project" + // Loop through original permissions and convert them + for _, perm := range permissions { + convertedPerm := &models.RobotPermission{ + Access: perm.Access, + Kind: kind, + Namespace: opts.Permissions[0].Namespace, + } + convertedPerms = append(convertedPerms, convertedPerm) + } + _, err = client.Robot.UpdateRobot( + ctx, + &robot.UpdateRobotParams{ + Robot: &models.Robot{ + Description: opts.Description, + Duration: opts.Duration, + Editable: opts.Editable, + Disable: opts.Disable, + ID: opts.ID, + Level: opts.Level, + Name: opts.Name, + Permissions: convertedPerms, + }, + RobotID: opts.ID, + }, + ) + if err != nil { + log.Errorf("Error in updating Robot: %v", err) + return err + } + + log.Info("robot updated successfully.") + return nil +} + func GetPermissions() (*permissions.GetPermissionsOK, error) { ctx, client, err := utils.ContextWithClient() if err != nil { diff --git a/pkg/prompt/prompt.go b/pkg/prompt/prompt.go index 21c4445d..acc412cc 100644 --- a/pkg/prompt/prompt.go +++ b/pkg/prompt/prompt.go @@ -14,8 +14,11 @@ package prompt import ( + "strconv" + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" "github.com/goharbor/harbor-cli/pkg/api" + "github.com/goharbor/harbor-cli/pkg/constants" aview "github.com/goharbor/harbor-cli/pkg/views/artifact/select" tview "github.com/goharbor/harbor-cli/pkg/views/artifact/tags/select" lview "github.com/goharbor/harbor-cli/pkg/views/label/select" @@ -128,3 +131,15 @@ func GetRobotPermissionsFromUser() []models.Permission { }() return <-permissions } + +func GetRobotIDFromUser(projectID int64) int64 { + robotID := make(chan int64) + var opts api.ListFlags + opts.Q = constants.ProjectQString + strconv.FormatInt(projectID, 10) + + go func() { + response, _ := api.ListRobot(opts) + robotView.ListRobot(response.Payload, robotID) + }() + return <-robotID +} diff --git a/pkg/views/base/multiselect/model.go b/pkg/views/base/multiselect/model.go index b6396a7b..fe1b61eb 100644 --- a/pkg/views/base/multiselect/model.go +++ b/pkg/views/base/multiselect/model.go @@ -2,6 +2,8 @@ package multiselect import ( "fmt" + "log" + "os" "strings" "github.com/charmbracelet/bubbles/viewport" @@ -19,9 +21,9 @@ var ( return lipgloss.NewStyle().BorderStyle(b).Padding(0, 1) }() - selectedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("43")) - itemStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("46")) - blockStyle = lipgloss.NewStyle(). + selectedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("43")) + itemStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("46")) + blockStyle = lipgloss.NewStyle(). Background(lipgloss.Color("81")). Foreground(lipgloss.Color("#000000")). Bold(true). @@ -170,7 +172,6 @@ func (m Model) listView() string { return s } - func (m Model) GetSelectedPermissions() *[]models.Permission { selectedPermissions := make([]models.Permission, 0, len(m.selected)) for index := range m.selected { diff --git a/pkg/views/robot/list/view.go b/pkg/views/robot/list/view.go index 5a1d39bf..1a04972e 100644 --- a/pkg/views/robot/list/view.go +++ b/pkg/views/robot/list/view.go @@ -16,6 +16,7 @@ import ( ) var columns = []table.Column{ + {Title: "ID", Width: 4}, {Title: "Name", Width: 30}, {Title: "Status", Width: 18}, {Title: "Permissions", Width: 12}, @@ -47,10 +48,11 @@ func ListRobots(robots []*models.Robot) { createdTime, _ := utils.FormatCreatedTime(robot.CreationTime.String()) rows = append(rows, table.Row{ - robot.Name, // Project Name - enabledStatus, // Access Level + strconv.FormatInt(robot.ID, 10), + robot.Name, + enabledStatus, TotalPermissions, - createdTime, // Creation Time + createdTime, expires, robot.Description, }) diff --git a/pkg/views/robot/select/view.go b/pkg/views/robot/select/view.go index cc76dceb..01d9f186 100644 --- a/pkg/views/robot/select/view.go +++ b/pkg/views/robot/select/view.go @@ -2,10 +2,14 @@ package robot import ( "fmt" + "log" + "os" + "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" "github.com/goharbor/go-client/pkg/sdk/v2.0/models" "github.com/goharbor/harbor-cli/pkg/views/base/multiselect" + "github.com/goharbor/harbor-cli/pkg/views/base/selection" ) func ListPermissions(perms *models.Permissions, ch chan<- []models.Permission) { @@ -21,10 +25,33 @@ func ListPermissions(perms *models.Permissions, ch chan<- []models.Permission) { m := multiselect.NewModel(choices, selects) - _, err := tea.NewProgram(m).Run() + _, err := tea.NewProgram(m, tea.WithAltScreen()).Run() if err != nil { fmt.Println("Error running program:", err) } // Get selected permissions ch <- *selects } + +func ListRobot(robots []*models.Robot, choice chan<- int64) { + itemsList := make([]list.Item, len(robots)) + + items := map[string]int64{} + + for i, r := range robots { + items[r.Name] = r.ID + itemsList[i] = selection.Item(r.Name) + } + + m := selection.NewModel(itemsList, "Robot") + + p, err := tea.NewProgram(m, tea.WithAltScreen()).Run() + if err != nil { + fmt.Println("Error running program:", err) + os.Exit(1) + } + + if p, ok := p.(selection.Model); ok { + choice <- items[p.Choice] + } +} diff --git a/pkg/views/robot/update/view.go b/pkg/views/robot/update/view.go new file mode 100644 index 00000000..a91efade --- /dev/null +++ b/pkg/views/robot/update/view.go @@ -0,0 +1,74 @@ +package update + +import ( + "errors" + "strconv" + + "github.com/charmbracelet/huh" + "github.com/go-openapi/strfmt" + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" + log "github.com/sirupsen/logrus" +) + +type UpdateView struct { + CreationTime strfmt.DateTime `json:"creation_time,omitempty"` + Description string `json:"description,omitempty"` + Disable bool `json:"disable,omitempty"` + Duration int64 `json:"duration,omitempty"` + Editable bool `json:"editable"` + ExpiresAt int64 `json:"expires_at,omitempty"` + ID int64 `json:"id,omitempty"` + Level string `json:"level,omitempty"` + Name string `json:"name,omitempty"` + Permissions []*RobotPermission `json:"permissions"` + Secret string `json:"secret,omitempty"` + UpdateTime strfmt.DateTime `json:"update_time,omitempty"` +} + +type RobotPermission struct { + Access []*models.Access `json:"access"` + Kind string `json:"kind,omitempty"` + Namespace string `json:"namespace,omitempty"` +} + +type Access struct { + Action string `json:"action,omitempty"` + Effect string `json:"effect,omitempty"` + Resource string `json:"resource,omitempty"` +} + +func UpdateRobotView(updateView *UpdateView) { + var duration string + duration = strconv.FormatInt(updateView.Duration, 10) + + theme := huh.ThemeCharm() + err := huh.NewForm( + huh.NewGroup( + huh.NewInput(). + Title("Description"). + Value(&updateView.Description), + huh.NewInput(). + Title("Expiration"). + Value(&duration). + Validate(func(str string) error { + if str == "" { + return errors.New("Expiration cannot be empty") + } + dur, err := strconv.ParseInt(str, 10, 64) + if err != nil { + return errors.New("invalid expiration time: Enter expiration time in days") + } + updateView.Duration = dur + return nil + }), + huh.NewConfirm(). + Title("Disable"). + Value(&updateView.Disable). + Affirmative("yes"). + Negative("no"), + ), + ).WithTheme(theme).Run() + if err != nil { + log.Fatal(err) + } +} From d44fea12654a00a7c90ae08675658ae09f52e837 Mon Sep 17 00:00:00 2001 From: bupd Date: Tue, 4 Jun 2024 20:43:57 +0530 Subject: [PATCH 5/7] add: robot refresh command & handle robot create Signed-off-by: bupd --- cmd/harbor/root/project/robot.go | 1 + cmd/harbor/root/project/robot/create.go | 10 ++- cmd/harbor/root/project/robot/refresh.go | 84 ++++++++++++++++++++++++ pkg/api/robot_handler.go | 27 +++++++- pkg/utils/utils.go | 30 +++++++++ pkg/views/base/multiselect/model.go | 2 - pkg/views/robot/create/view.go | 12 +--- pkg/views/robot/select/view.go | 1 - 8 files changed, 148 insertions(+), 19 deletions(-) create mode 100644 cmd/harbor/root/project/robot/refresh.go diff --git a/cmd/harbor/root/project/robot.go b/cmd/harbor/root/project/robot.go index 12a8a619..368ed7cf 100644 --- a/cmd/harbor/root/project/robot.go +++ b/cmd/harbor/root/project/robot.go @@ -17,6 +17,7 @@ func Robot() *cobra.Command { robot.ViewRobotCommand(), robot.CreateRobotCommand(), robot.UpdateRobotCommand(), + robot.RefreshSecretCommand(), ) return cmd diff --git a/cmd/harbor/root/project/robot/create.go b/cmd/harbor/root/project/robot/create.go index 87b6f9a0..7840e815 100644 --- a/cmd/harbor/root/project/robot/create.go +++ b/cmd/harbor/root/project/robot/create.go @@ -1,6 +1,7 @@ package robot import ( + "fmt" "os" "github.com/atotto/clipboard" @@ -14,7 +15,6 @@ import ( "github.com/spf13/viper" ) -// to-do add json file as input and getting json file as output from input. func CreateRobotCommand() *cobra.Command { var ( opts create.CreateView @@ -88,12 +88,16 @@ func CreateRobotCommand() *cobra.Command { FormatFlag := viper.GetString("output-format") if FormatFlag != "" { - utils.PrintPayloadInJSONFormat(response.Payload) + name := response.Payload.Name + res, _ := api.GetRobot(response.Payload.ID) + utils.SavePayloadJSON(name, res.Payload) return } - create.CreateRobotSecretView(response.Payload) + name, secret := response.Payload.Name, response.Payload.Secret + create.CreateRobotSecretView(name, secret) err = clipboard.WriteAll(response.Payload.Secret) + fmt.Println("secret copied to clipboard.") }, } flags := cmd.Flags() diff --git a/cmd/harbor/root/project/robot/refresh.go b/cmd/harbor/root/project/robot/refresh.go new file mode 100644 index 00000000..baafb6b7 --- /dev/null +++ b/cmd/harbor/root/project/robot/refresh.go @@ -0,0 +1,84 @@ +package robot + +import ( + "fmt" + "os" + "strconv" + + "github.com/atotto/clipboard" + "github.com/goharbor/harbor-cli/pkg/api" + "github.com/goharbor/harbor-cli/pkg/prompt" + "github.com/goharbor/harbor-cli/pkg/utils" + "github.com/goharbor/harbor-cli/pkg/views/robot/create" + log "github.com/sirupsen/logrus" + + "github.com/spf13/cobra" +) + +// handle robot view with interactive like in list command. +func RefreshSecretCommand() *cobra.Command { + var ( + robotID int64 + secret string + secretStdin bool + ) + cmd := &cobra.Command{ + Use: "refresh [robotID]", + Short: "refresh robot secret by id", + Args: cobra.MaximumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + var err error + if len(args) == 1 { + robotID, err = strconv.ParseInt(args[0], 10, 64) + if err != nil { + log.Errorf("failed to parse robot ID: %v", err) + } + } else { + projectID := prompt.GetProjectIDFromUser() + robotID = prompt.GetRobotIDFromUser(projectID) + } + + if secret != "" { + utils.ValidatePassword(secret) + } + if secretStdin { + secret = getSecret() + } + + response, err := api.RefreshSecret(secret, robotID) + if err != nil { + log.Errorf("failed to refresh robot secret.") + os.Exit(1) + } + + log.Info("Secret updated successfully.") + + secret = response.Payload.Secret + create.CreateRobotSecretView("", secret) + + err = clipboard.WriteAll(response.Payload.Secret) + fmt.Println("secret copied to clipboard.") + }, + } + + flags := cmd.Flags() + flags.StringVarP(&secret, "secret", "", "", "secret") + flags.BoolVarP(&secretStdin, "secret-stdin", "", false, "Take the robot secret from stdin") + + return cmd +} + +// getSecret from commandline +func getSecret() string { + secret, err := utils.GetSecretStdin("Enter your secret: ") + if err != nil { + log.Errorf("Error reading secret: %v\n", err) + os.Exit(1) + } + + if err := utils.ValidatePassword(secret); err != nil { + log.Errorf("Invalid secret: %v\n", err) + os.Exit(1) + } + return secret +} diff --git a/pkg/api/robot_handler.go b/pkg/api/robot_handler.go index 669bbe01..ad44db6e 100644 --- a/pkg/api/robot_handler.go +++ b/pkg/api/robot_handler.go @@ -119,9 +119,9 @@ func UpdateRobot(opts *update.UpdateView) error { // Loop through original permissions and convert them for _, perm := range permissions { convertedPerm := &models.RobotPermission{ - Access: perm.Access, - Kind: kind, - Namespace: opts.Permissions[0].Namespace, + Access: perm.Access, + Kind: kind, + Namespace: opts.Permissions[0].Namespace, } convertedPerms = append(convertedPerms, convertedPerm) } @@ -165,3 +165,24 @@ func GetPermissions() (*permissions.GetPermissionsOK, error) { return response, nil } + +func RefreshSecret(secret string, robotID int64) (*robot.RefreshSecOK, error) { + ctx, client, err := utils.ContextWithClient() + if err != nil { + return nil, err + } + + robotSec := &models.RobotSec{ + Secret: secret, + } + + response, err := client.Robot.RefreshSec(ctx, &robot.RefreshSecParams{ + RobotSec: robotSec, + RobotID: robotID, + }) + if err != nil { + return nil, err + } + + return response, nil +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index bc027c71..df6aec66 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -17,10 +17,14 @@ import ( "encoding/json" "fmt" "regexp" + "os" "strings" + "syscall" + "unicode" log "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" + "golang.org/x/term" ) // Returns Harbor v2 client for given clientConfig @@ -74,3 +78,29 @@ func SanitizeServerAddress(server string) string { server = re.ReplaceAllString(server, "-") return server } + +func SavePayloadJSON(filename string, payload any) { + // Marshal the payload into a JSON string with indentation + jsonStr, err := json.MarshalIndent(payload, "", " ") + if err != nil { + panic(err) + } + // Define the filename + filename = filename + ".json" + err = os.WriteFile(filename, jsonStr, 0644) + if err != nil { + panic(err) + } + fmt.Printf("JSON data has been written to %s\n", filename) +} + +// Get Password as Stdin +func GetSecretStdin(prompt string) (string, error) { + fmt.Print(prompt) + bytePassword, err := term.ReadPassword(int(syscall.Stdin)) + if err != nil { + return "", err + } + fmt.Println() // move to the next line after input + return strings.TrimSpace(string(bytePassword)), nil +} diff --git a/pkg/views/base/multiselect/model.go b/pkg/views/base/multiselect/model.go index fe1b61eb..0d31b8de 100644 --- a/pkg/views/base/multiselect/model.go +++ b/pkg/views/base/multiselect/model.go @@ -2,8 +2,6 @@ package multiselect import ( "fmt" - "log" - "os" "strings" "github.com/charmbracelet/bubbles/viewport" diff --git a/pkg/views/robot/create/view.go b/pkg/views/robot/create/view.go index bb8404a6..9012c87d 100644 --- a/pkg/views/robot/create/view.go +++ b/pkg/views/robot/create/view.go @@ -72,21 +72,13 @@ func CreateRobotView(createView *CreateView) { } } -func CreateRobotSecretView(response *models.RobotCreated) { - name := response.Name - secret := response.Secret +func CreateRobotSecretView(name string, secret string) { theme := huh.ThemeCharm() err := huh.NewForm( huh.NewGroup( huh.NewInput(). Title("Robot Name"). - Value(&name). - Validate(func(str string) error { - if str == "" { - return errors.New("Name cannot be empty") - } - return nil - }), + Value(&name), huh.NewInput(). Title("Secret"). Value(&secret), diff --git a/pkg/views/robot/select/view.go b/pkg/views/robot/select/view.go index 01d9f186..c99afc92 100644 --- a/pkg/views/robot/select/view.go +++ b/pkg/views/robot/select/view.go @@ -2,7 +2,6 @@ package robot import ( "fmt" - "log" "os" "github.com/charmbracelet/bubbles/list" From d7365f8033f93011853551cdd8b3212e5eba32eb Mon Sep 17 00:00:00 2001 From: bupd Date: Thu, 6 Jun 2024 03:10:56 +0530 Subject: [PATCH 6/7] project robot commands refactor Signed-off-by: bupd --- cmd/harbor/root/project/robot.go | 2 +- cmd/harbor/root/project/robot/create.go | 19 +++------------ cmd/harbor/root/project/robot/delete.go | 22 +++++++++++------ cmd/harbor/root/project/robot/list.go | 9 +++---- cmd/harbor/root/project/robot/refresh.go | 31 +++++++++++++----------- cmd/harbor/root/project/robot/update.go | 9 ++++--- cmd/harbor/root/project/robot/view.go | 30 ++++++++++++++--------- pkg/api/robot_handler.go | 12 +++------ pkg/views/base/multiselect/model.go | 6 ++--- pkg/views/robot/create/view.go | 5 ++-- pkg/views/robot/list/view.go | 2 +- 11 files changed, 77 insertions(+), 70 deletions(-) diff --git a/cmd/harbor/root/project/robot.go b/cmd/harbor/root/project/robot.go index 368ed7cf..9d1d2bec 100644 --- a/cmd/harbor/root/project/robot.go +++ b/cmd/harbor/root/project/robot.go @@ -9,7 +9,7 @@ func Robot() *cobra.Command { cmd := &cobra.Command{ Use: "robot", Short: "Manage robot accounts", - Example: ` harbor robot list`, + Example: ` harbor project robot list`, } cmd.AddCommand( robot.ListRobotCommand(), diff --git a/cmd/harbor/root/project/robot/create.go b/cmd/harbor/root/project/robot/create.go index 7840e815..fc9ca036 100644 --- a/cmd/harbor/root/project/robot/create.go +++ b/cmd/harbor/root/project/robot/create.go @@ -2,7 +2,6 @@ package robot import ( "fmt" - "os" "github.com/atotto/clipboard" "github.com/goharbor/go-client/pkg/sdk/v2.0/models" @@ -25,26 +24,17 @@ func CreateRobotCommand() *cobra.Command { cmd := &cobra.Command{ Use: "create", Short: "create robot", - Args: cobra.MaximumNArgs(1), + Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { var err error - kind := "project" - opts.Level = kind if opts.ProjectName == "" { opts.ProjectName = prompt.GetProjectNameFromUser() if opts.ProjectName == "" { - os.Exit(1) + log.Fatalf("Project Name Cannot be empty") } } - // to-do handle permission as json submission - perm := &create.RobotPermission{ - Kind: kind, - Namespace: projectName, - } - opts.Permissions = []*create.RobotPermission{perm} - if len(args) == 0 { if opts.Name == "" || opts.Duration == 0 { create.CreateRobotView(&opts) @@ -75,15 +65,14 @@ func CreateRobotCommand() *cobra.Command { } // convert []models.permission to []*model.Access perm := &create.RobotPermission{ - Kind: kind, Namespace: projectName, Access: accesses, } opts.Permissions = []*create.RobotPermission{perm} } - response, err := api.CreateRobot(opts) + response, err := api.CreateRobot(opts, "project") if err != nil { - log.Errorf("failed to create robot: %v", err) + log.Fatalf("failed to create robot: %v", err) } FormatFlag := viper.GetString("output-format") diff --git a/cmd/harbor/root/project/robot/delete.go b/cmd/harbor/root/project/robot/delete.go index 09015bdf..d2641fe9 100644 --- a/cmd/harbor/root/project/robot/delete.go +++ b/cmd/harbor/root/project/robot/delete.go @@ -4,27 +4,35 @@ import ( "strconv" "github.com/goharbor/harbor-cli/pkg/api" + "github.com/goharbor/harbor-cli/pkg/prompt" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) -// to-do improve DeleteRobotCommand and multi delete +// to-do improve DeleteRobotCommand and multi select & delete func DeleteRobotCommand() *cobra.Command { cmd := &cobra.Command{ Use: "delete [robotID]", Short: "delete robot by id", Args: cobra.MaximumNArgs(1), Run: func(cmd *cobra.Command, args []string) { + var ( + robotID int64 + err error + ) if len(args) == 1 { - robotID, err := strconv.ParseInt(args[0], 10, 64) + robotID, err = strconv.ParseInt(args[0], 10, 64) if err != nil { - log.Errorf("failed to parse robot ID: %v", err) - } - err = api.DeleteRobot(robotID) - if err != nil { - log.Errorf("failed to Delete robots") + log.Fatalf("failed to parse robot ID: %v", err) } + } else { + projectID := prompt.GetProjectIDFromUser() + robotID = prompt.GetRobotIDFromUser(projectID) + } + err = api.DeleteRobot(robotID) + if err != nil { + log.Fatalf("failed to Delete robots") } }, } diff --git a/cmd/harbor/root/project/robot/list.go b/cmd/harbor/root/project/robot/list.go index 2afe9c38..db1b3fd7 100644 --- a/cmd/harbor/root/project/robot/list.go +++ b/cmd/harbor/root/project/robot/list.go @@ -15,10 +15,7 @@ import ( // ListRobotCommand creates a new `harbor project robot list` command func ListRobotCommand() *cobra.Command { - var ( - query string - opts api.ListFlags - ) + var opts api.ListFlags projectQString := constants.ProjectQString cmd := &cobra.Command{ @@ -28,6 +25,8 @@ func ListRobotCommand() *cobra.Command { Run: func(cmd *cobra.Command, args []string) { if len(args) > 0 { opts.Q = projectQString + args[0] + } else if opts.Q != "" { + opts.Q = projectQString + opts.Q } else { projectID := prompt.GetProjectIDFromUser() opts.Q = projectQString + strconv.FormatInt(projectID, 10) @@ -51,7 +50,7 @@ func ListRobotCommand() *cobra.Command { flags := cmd.Flags() flags.Int64VarP(&opts.Page, "page", "", 1, "Page number") flags.Int64VarP(&opts.PageSize, "page-size", "", 10, "Size of per page") - flags.StringVarP(&query, "query", "q", "", "Query string to query resources") + flags.StringVarP(&opts.Q, "query", "q", "", "Query string to query resources") flags.StringVarP( &opts.Sort, "sort", diff --git a/cmd/harbor/root/project/robot/refresh.go b/cmd/harbor/root/project/robot/refresh.go index baafb6b7..46321e91 100644 --- a/cmd/harbor/root/project/robot/refresh.go +++ b/cmd/harbor/root/project/robot/refresh.go @@ -2,7 +2,6 @@ package robot import ( "fmt" - "os" "strconv" "github.com/atotto/clipboard" @@ -15,7 +14,6 @@ import ( "github.com/spf13/cobra" ) -// handle robot view with interactive like in list command. func RefreshSecretCommand() *cobra.Command { var ( robotID int64 @@ -31,7 +29,7 @@ func RefreshSecretCommand() *cobra.Command { if len(args) == 1 { robotID, err = strconv.ParseInt(args[0], 10, 64) if err != nil { - log.Errorf("failed to parse robot ID: %v", err) + log.Fatalf("failed to parse robot ID: %v", err) } } else { projectID := prompt.GetProjectIDFromUser() @@ -39,7 +37,10 @@ func RefreshSecretCommand() *cobra.Command { } if secret != "" { - utils.ValidatePassword(secret) + err = utils.ValidatePassword(secret) + if err != nil { + log.Fatalf("Invalid secret: %v\n", err) + } } if secretStdin { secret = getSecret() @@ -47,17 +48,21 @@ func RefreshSecretCommand() *cobra.Command { response, err := api.RefreshSecret(secret, robotID) if err != nil { - log.Errorf("failed to refresh robot secret.") - os.Exit(1) + log.Fatalf("failed to refresh robot secret: %v\n", err) } log.Info("Secret updated successfully.") - secret = response.Payload.Secret - create.CreateRobotSecretView("", secret) + if response.Payload.Secret != "" { + secret = response.Payload.Secret + create.CreateRobotSecretView("", secret) - err = clipboard.WriteAll(response.Payload.Secret) - fmt.Println("secret copied to clipboard.") + err = clipboard.WriteAll(response.Payload.Secret) + if err != nil { + log.Fatalf("failed to write the secret to the clipboard: %v", err) + } + fmt.Println("secret copied to clipboard.") + } }, } @@ -72,13 +77,11 @@ func RefreshSecretCommand() *cobra.Command { func getSecret() string { secret, err := utils.GetSecretStdin("Enter your secret: ") if err != nil { - log.Errorf("Error reading secret: %v\n", err) - os.Exit(1) + log.Fatalf("Error reading secret: %v\n", err) } if err := utils.ValidatePassword(secret); err != nil { - log.Errorf("Invalid secret: %v\n", err) - os.Exit(1) + log.Fatalf("Invalid secret: %v\n", err) } return secret } diff --git a/cmd/harbor/root/project/robot/update.go b/cmd/harbor/root/project/robot/update.go index 53cb1ce5..896062cf 100644 --- a/cmd/harbor/root/project/robot/update.go +++ b/cmd/harbor/root/project/robot/update.go @@ -12,7 +12,6 @@ import ( "github.com/spf13/cobra" ) -// to-do complete UpdateRobotCommand func UpdateRobotCommand() *cobra.Command { var ( robotID int64 @@ -29,7 +28,7 @@ func UpdateRobotCommand() *cobra.Command { if len(args) == 1 { robotID, err = strconv.ParseInt(args[0], 10, 64) if err != nil { - log.Errorf("failed to parse robot ID: %v", err) + log.Fatalf("failed to parse robot ID: %v", err) } } else { @@ -38,6 +37,10 @@ func UpdateRobotCommand() *cobra.Command { } robot, err := api.GetRobot(robotID) + if err != nil { + log.Fatalf("failed to get robot: %v", err) + } + bot := robot.Payload opts = update.UpdateView{ @@ -87,7 +90,7 @@ func UpdateRobotCommand() *cobra.Command { err = updateRobotView(&opts) if err != nil { - log.Errorf("failed to Update robot") + log.Fatalf("failed to Update robot: %v", err) } }, } diff --git a/cmd/harbor/root/project/robot/view.go b/cmd/harbor/root/project/robot/view.go index e138253a..e3ac7cff 100644 --- a/cmd/harbor/root/project/robot/view.go +++ b/cmd/harbor/root/project/robot/view.go @@ -1,38 +1,46 @@ package robot import ( - "os" "strconv" "github.com/goharbor/go-client/pkg/sdk/v2.0/client/robot" "github.com/goharbor/go-client/pkg/sdk/v2.0/models" "github.com/goharbor/harbor-cli/pkg/api" + "github.com/goharbor/harbor-cli/pkg/prompt" "github.com/goharbor/harbor-cli/pkg/views/robot/list" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) -// handle robot view with interactive like in list command. func ViewRobotCommand() *cobra.Command { cmd := &cobra.Command{ Use: "view [robotID]", Short: "get robot by id", - Args: cobra.ExactArgs(1), + Args: cobra.MaximumNArgs(1), Run: func(cmd *cobra.Command, args []string) { - var robot *robot.GetRobotByIDOK + var ( + robot *robot.GetRobotByIDOK + robotID int64 + err error + ) if len(args) == 1 { - robotID, err := strconv.ParseInt(args[0], 10, 64) + robotID, err = strconv.ParseInt(args[0], 10, 64) if err != nil { - log.Errorf("failed to parse robot ID: %v", err) - } - robot, err = api.GetRobot(robotID) - if err != nil { - log.Errorf("failed to List robots") - os.Exit(1) + log.Fatalf("failed to parse robot ID: %v", err) } + } else { + projectID := prompt.GetProjectIDFromUser() + robotID = prompt.GetRobotIDFromUser(projectID) } + + robot, err = api.GetRobot(robotID) + if err != nil { + log.Fatalf("failed to get robot: %v", err) + } + + // Convert to a list and display robots := []*models.Robot{robot.Payload} list.ListRobots(robots) }, diff --git a/pkg/api/robot_handler.go b/pkg/api/robot_handler.go index ad44db6e..287eb140 100644 --- a/pkg/api/robot_handler.go +++ b/pkg/api/robot_handler.go @@ -60,7 +60,7 @@ func DeleteRobot(robotID int64) error { return nil } -func CreateRobot(opts create.CreateView) (*robot.CreateRobotCreated, error) { +func CreateRobot(opts create.CreateView, kind string) (*robot.CreateRobotCreated, error) { ctx, client, err := utils.ContextWithClient() if err != nil { return nil, err @@ -70,12 +70,11 @@ func CreateRobot(opts create.CreateView) (*robot.CreateRobotCreated, error) { permissions := opts.Permissions convertedPerms := make([]*models.RobotPermission, 0, len(permissions)) - project := "project" // Loop through original permissions and convert them for _, perm := range permissions { convertedPerm := &models.RobotPermission{ Access: perm.Access, - Kind: project, + Kind: kind, Namespace: opts.ProjectName, } convertedPerms = append(convertedPerms, convertedPerm) @@ -87,7 +86,7 @@ func CreateRobot(opts create.CreateView) (*robot.CreateRobotCreated, error) { Description: opts.Description, Disable: false, Duration: opts.Duration, - Level: opts.Level, + Level: kind, Name: opts.Name, Permissions: convertedPerms, }, @@ -109,18 +108,15 @@ func UpdateRobot(opts *update.UpdateView) error { return err } - log.Println(opts) - // Create a slice to store converted permissions permissions := opts.Permissions convertedPerms := make([]*models.RobotPermission, 0, len(permissions)) - kind := "project" // Loop through original permissions and convert them for _, perm := range permissions { convertedPerm := &models.RobotPermission{ Access: perm.Access, - Kind: kind, + Kind: opts.Permissions[0].Kind, Namespace: opts.Permissions[0].Namespace, } convertedPerms = append(convertedPerms, convertedPerm) diff --git a/pkg/views/base/multiselect/model.go b/pkg/views/base/multiselect/model.go index 0d31b8de..569591d3 100644 --- a/pkg/views/base/multiselect/model.go +++ b/pkg/views/base/multiselect/model.go @@ -38,9 +38,9 @@ type Model struct { content string ready bool viewport viewport.Model - choices []models.Permission // items on the to-do list - cursor int // which to-do list item our cursor is pointing at - selected map[int]struct{} // which to-do items are selected + choices []models.Permission + cursor int + selected map[int]struct{} selects *[]models.Permission } diff --git a/pkg/views/robot/create/view.go b/pkg/views/robot/create/view.go index 9012c87d..1108b9eb 100644 --- a/pkg/views/robot/create/view.go +++ b/pkg/views/robot/create/view.go @@ -40,7 +40,7 @@ func CreateRobotView(createView *CreateView) { err := huh.NewForm( huh.NewGroup( huh.NewInput(). - Title("Name"). + Title("Robot Name"). Value(&createView.Name). Validate(func(str string) error { if str == "" { @@ -80,7 +80,8 @@ func CreateRobotSecretView(name string, secret string) { Title("Robot Name"). Value(&name), huh.NewInput(). - Title("Secret"). + Title("Robot Secret"). + Description("Copy the secret or press enter to copy to clipboard."). Value(&secret), ), ).WithTheme(theme).Run() diff --git a/pkg/views/robot/list/view.go b/pkg/views/robot/list/view.go index 1a04972e..adab0c4c 100644 --- a/pkg/views/robot/list/view.go +++ b/pkg/views/robot/list/view.go @@ -38,7 +38,7 @@ func ListRobots(robots []*models.Robot) { enabledStatus = views.GreenStyle.Render("Enabled") } - TotalPermissions := strconv.FormatInt(int64(len(robot.Permissions)), 10) + TotalPermissions := strconv.FormatInt(int64(len(robot.Permissions[0].Access)), 10) if robot.ExpiresAt == -1 { expires = "Never" From 5400f2deff98b63ae46a1229d408c3ec71ce1ddc Mon Sep 17 00:00:00 2001 From: bupd Date: Thu, 20 Feb 2025 04:57:18 +0530 Subject: [PATCH 7/7] update utils Signed-off-by: bupd --- pkg/utils/utils.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index df6aec66..c543ddc2 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -16,15 +16,14 @@ package utils import ( "encoding/json" "fmt" - "regexp" "os" + "regexp" "strings" "syscall" - "unicode" log "github.com/sirupsen/logrus" - "gopkg.in/yaml.v3" "golang.org/x/term" + "gopkg.in/yaml.v3" ) // Returns Harbor v2 client for given clientConfig