Skip to content

Commit

Permalink
add: robot refresh command & handle robot create
Browse files Browse the repository at this point in the history
Signed-off-by: bupd <[email protected]>
  • Loading branch information
bupd committed Jun 5, 2024
1 parent 53bbbb5 commit ae787d1
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 19 deletions.
1 change: 1 addition & 0 deletions cmd/harbor/root/project/robot.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ func Robot() *cobra.Command {
robot.ViewRobotCommand(),
robot.CreateRobotCommand(),
robot.UpdateRobotCommand(),
robot.RefreshSecretCommand(),
)

return cmd
Expand Down
10 changes: 7 additions & 3 deletions cmd/harbor/root/project/robot/create.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package robot

import (
"fmt"
"os"

"github.com/atotto/clipboard"
Expand All @@ -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
Expand Down Expand Up @@ -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()
Expand Down
84 changes: 84 additions & 0 deletions cmd/harbor/root/project/robot/refresh.go
Original file line number Diff line number Diff line change
@@ -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
}
27 changes: 24 additions & 3 deletions pkg/api/robot_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
}
62 changes: 62 additions & 0 deletions pkg/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ package utils
import (
"encoding/json"
"fmt"
"os"
"strings"
"syscall"
"unicode"

log "github.com/sirupsen/logrus"
"golang.org/x/term"
)

// Returns Harbor v2 client for given clientConfig
Expand Down Expand Up @@ -38,3 +42,61 @@ func ParseProjectRepoReference(projectRepoReference string) (string, string, str
}
return split[0], split[1], split[2]
}

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)
}

// Validate the secret based on the provided guidelines
func ValidatePassword(s string) error {
const (
minLength = 8
maxLength = 128
)

var (
hasLen = false
hasUpper = false
hasLower = false
hasNumber = false
)
if len(s) >= minLength && len(s) <= maxLength {
hasLen = true
}
for _, char := range s {
switch {
case unicode.IsUpper(char):
hasUpper = true
case unicode.IsLower(char):
hasLower = true
case unicode.IsNumber(char):
hasNumber = true
}
}
if hasLen && hasUpper && hasLower && hasNumber {
return nil
}
return fmt.Errorf("secret should contain at least 1 uppercase, 1 lowercase and 1 number.")
}

// 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
}
2 changes: 0 additions & 2 deletions pkg/views/base/multiselect/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package multiselect

import (
"fmt"
"log"
"os"
"strings"

"github.com/charmbracelet/bubbles/viewport"
Expand Down
12 changes: 2 additions & 10 deletions pkg/views/robot/create/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
1 change: 0 additions & 1 deletion pkg/views/robot/select/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package robot

import (
"fmt"
"log"
"os"

"github.com/charmbracelet/bubbles/list"
Expand Down

0 comments on commit ae787d1

Please sign in to comment.