Skip to content

Refactor: Make Error logging more user friendly for project commands #424

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions cmd/harbor/root/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,16 @@ harbor help
utils.InitConfig(cfgFile, userSpecifiedConfig)

// Conditionally set the timestamp format only in verbose mode
formatter := &logrus.TextFormatter{}

if verbose {
logrus.SetFormatter(&logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: time.RFC3339,
})
formatter.FullTimestamp = true
formatter.TimestampFormat = time.RFC3339
logrus.SetLevel(logrus.DebugLevel)
} else {
// No timestamp format for non-verbose
logrus.SetFormatter(&logrus.TextFormatter{
DisableTimestamp: true,
})
formatter.DisableTimestamp = true
}
logrus.SetFormatter(formatter)

return nil
},
Expand Down
44 changes: 28 additions & 16 deletions cmd/harbor/root/project/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
package project

import (
"fmt"

"github.com/goharbor/harbor-cli/pkg/api"
"github.com/goharbor/harbor-cli/pkg/views/project/create"
log "github.com/sirupsen/logrus"
Expand All @@ -28,32 +30,42 @@ func CreateProjectCommand() *cobra.Command {
Use: "create [project name]",
Short: "create project",
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
var err error
createView := &create.CreateView{
ProjectName: opts.ProjectName,
Public: opts.Public,
RegistryID: opts.RegistryID,
StorageLimit: opts.StorageLimit,
ProxyCache: false,
}
var ProjectName string
if len(args) > 0 {
opts.ProjectName = args[0]
}

if opts.ProxyCache && opts.RegistryID == "" {
log.Errorf("Use the --registry-id flag with a registry ID")
} else {
err = api.CreateProject(opts)
}
if opts.ProxyCache && opts.RegistryID == "" {
return fmt.Errorf("Error: Proxy cache selected but no registry ID provided. Use --registry-id.")
}

if opts.ProjectName != "" {
log.Debug("Attempting to create project using flags...")
err = api.CreateProject(opts)
ProjectName = opts.ProjectName
} else {
log.Debug("No project name provided. Switching to interactive view...")
createView := &create.CreateView{
ProjectName: opts.ProjectName,
Public: opts.Public,
RegistryID: opts.RegistryID,
StorageLimit: opts.StorageLimit,
ProxyCache: opts.ProxyCache,
}

err = createProjectView(createView)
ProjectName = createView.ProjectName
}

if err != nil {
log.Errorf("failed to create project: %v", err)
return fmt.Errorf("Error: Failed to create project: %v\n", err)
}
},
}

fmt.Printf("Project '%s' created successfully\n", ProjectName)
return nil
}}

flags := cmd.Flags()
flags.BoolVarP(&opts.Public, "public", "", false, "Project is public or private")
Expand Down
80 changes: 56 additions & 24 deletions cmd/harbor/root/project/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,70 +14,102 @@
package project

import (
"fmt"
"sync"

"github.com/goharbor/harbor-cli/pkg/api"
"github.com/goharbor/harbor-cli/pkg/prompt"
"github.com/goharbor/harbor-cli/pkg/utils"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

// DeleteProjectCommand creates a new `harbor project delete` command
// DeleteProjectCommand creates a new `harbor delete project` command
func DeleteProjectCommand() *cobra.Command {
var forceDelete bool
var projectID string

cmd := &cobra.Command{
Use: "delete",
Short: "Delete project by name or ID",
Example: "harbor project delete [projectname] or harbor project delete --project-id [projectid]",
Long: "Delete project by name or ID. If no arguments are provided, it will prompt for the project name. Use --project-id to specify the project ID directly. The --force flag will delete all repositories and artifacts within the project.",
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
Example: "harbor project delete [projectname1] [projectname2] or harbor project delete --project-id [projectid]",
Long: "Delete project by name or ID. Multiple projects can be deleted by providing their names as arguments. If no arguments are provided, it will prompt for the project name. Use --project-id to specify the project ID for single project directly. The --force flag will delete all repositories and artifacts within the project.",
Args: cobra.MinimumNArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
var wg sync.WaitGroup
errChan := make(chan error, len(args))
var mu sync.Mutex

successfulDeletes := []string{}
failedDeletes := map[string]string{}

if projectID != "" {
log.Debugf("Deleting project with ID: %s", projectID)
wg.Add(1)
go func(id string) {
defer wg.Done()
if err := api.DeleteProject(id, forceDelete, true); err != nil {
errChan <- err
mu.Lock()
failedDeletes[id] = utils.ParseHarborError(err)
mu.Unlock()
} else {
mu.Lock()
successfulDeletes = append(successfulDeletes, id)
mu.Unlock()
}
}(projectID)
} else if len(args) > 0 {
// Delete by project name from args
log.Debugf("Deleting %d projects from args...", len(args))
for _, projectName := range args {
pn := projectName
log.Debugf("Initiating delete for project: %s", pn)
wg.Add(1)
go func(name string) {
go func(projectName string) {
defer wg.Done()
if err := api.DeleteProject(name, forceDelete, false); err != nil {
errChan <- err
log.Debugf("Deleting project '%s' with force=%v", projectName, forceDelete)
if err := api.DeleteProject(projectName, forceDelete, false); err != nil {
mu.Lock()
failedDeletes[projectName] = utils.ParseHarborError(err)
mu.Unlock()
} else {
mu.Lock()
successfulDeletes = append(successfulDeletes, projectName)
mu.Unlock()
}
}(projectName)
}(pn)
}
} else {
// If no arguments provided, prompt user for project name
log.Debug("No arguments provided. Prompting user for project name.")
projectName := prompt.GetProjectNameFromUser()
log.Debugf("User input project: %s", projectName)
log.Debugf("Deleting project '%s' with force=%v", projectName, forceDelete)
if err := api.DeleteProject(projectName, forceDelete, false); err != nil {
log.Errorf("failed to delete project: %v", err)
return fmt.Errorf("failed to delete project: %v", utils.ParseHarborError(err))
}
fmt.Printf("Project '%s' deleted successfully\n", projectName)
return nil
}

go func() {
wg.Wait()
close(errChan)
}()
wg.Wait()

var finalErr error
for err := range errChan {
if finalErr == nil {
finalErr = err
} else {
log.Errorf("Error: %v", err)
if len(successfulDeletes) > 0 {
fmt.Println("Successfully deleted projects:")
for _, name := range successfulDeletes {
fmt.Printf(" - %s\n", name)
}
}
if finalErr != nil {
log.Errorf("failed to delete some projects: %v", finalErr)

if len(failedDeletes) > 0 {
fmt.Println("Failed to delete projects:")
for name, reason := range failedDeletes {
fmt.Printf(" - %s: %s\n", name, reason)
}
return fmt.Errorf("failed to delete %d project(s)", len(failedDeletes))
}

log.Debug("All requested projects deleted successfully.")
return nil
},
}

Expand Down
29 changes: 27 additions & 2 deletions cmd/harbor/root/project/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,42 +37,52 @@ func ListProjectCommand() *cobra.Command {
Short: "List projects",
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
log.Debug("Starting project list command")

if opts.PageSize > 100 {
return fmt.Errorf("page size should be less than or equal to 100")
}

if private && public {
return fmt.Errorf("Cannot specify both --private and --public flags")
}

var listFunc func(...api.ListFlags) (project.ListProjectsOK, error)
if private {
log.Debug("Using private project list function")
opts.Public = false
listFunc = api.ListProject
} else if public {
log.Debug("Using public project list function")
opts.Public = true
listFunc = api.ListProject
} else {
log.Debug("Using list all projects function")
listFunc = api.ListAllProjects
}

log.Debug("Fetching projects...")
allProjects, err = fetchProjects(listFunc, opts)
if err != nil {
return fmt.Errorf("failed to get projects list: %v", err)
return fmt.Errorf("failed to get projects list: %v", utils.ParseHarborError(err))
}

log.WithField("count", len(allProjects)).Debug("Number of projects fetched")
if len(allProjects) == 0 {
log.Info("No projects found")
return nil
}
formatFlag := viper.GetString("output-format")
if formatFlag != "" {
log.WithField("output_format", formatFlag).Debug("Output format selected")
err = utils.PrintFormat(allProjects, formatFlag)
if err != nil {
return err
}
} else {
log.Debug("Listing projects using default view")
list.ListProjects(allProjects)
}

return nil
},
}
Expand All @@ -92,26 +102,41 @@ func ListProjectCommand() *cobra.Command {
func fetchProjects(listFunc func(...api.ListFlags) (project.ListProjectsOK, error), opts api.ListFlags) ([]*models.Project, error) {
var allProjects []*models.Project
if opts.PageSize == 0 {
log.Debug("Page size is 0, will fetch all pages")
opts.PageSize = 100
opts.Page = 1

for {
log.WithFields(log.Fields{
"page": opts.Page,
"page_size": opts.PageSize,
}).Debug("Fetching next page of projects")

projects, err := listFunc(opts)
if err != nil {
log.WithError(err).Error("Error fetching project page")
return nil, err
}

log.WithField("fetched_count", len(projects.Payload)).Debug("Fetched projects from current page")
allProjects = append(allProjects, projects.Payload...)

if len(projects.Payload) < int(opts.PageSize) {
log.Debug("Last page reached, stopping pagination")
break
}

opts.Page++
}
} else {
log.WithFields(log.Fields{
"page": opts.Page,
"page_size": opts.PageSize,
}).Debug("Fetching projects with user-defined pagination")

projects, err := listFunc(opts)
if err != nil {
log.WithError(err).Error("Error fetching paginated projects")
return nil, err
}
allProjects = projects.Payload
Expand Down
40 changes: 30 additions & 10 deletions cmd/harbor/root/project/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
package project

import (
"fmt"

proj "github.com/goharbor/go-client/pkg/sdk/v2.0/client/project"
"github.com/goharbor/harbor-cli/pkg/api"
"github.com/goharbor/harbor-cli/pkg/prompt"
Expand All @@ -32,30 +34,48 @@ func LogsProjectCommmand() *cobra.Command {
Use: "logs",
Short: "get project logs",
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
log.Debug("Starting execution of 'logs' command")
var err error
var resp *proj.GetLogsOK
var projectName string

if len(args) > 0 {
resp, err = api.LogsProject(args[0])
projectName = args[0]
log.Debugf("Project name provided as argument: %s", projectName)
} else {
projectName := prompt.GetProjectNameFromUser()
resp, err = api.LogsProject(projectName)
log.Debug("No project name argument provided, prompting user...")
projectName = prompt.GetProjectNameFromUser()
log.Debugf("Project name received from prompt: %s", projectName)
}

log.Debugf("Checking if project '%s' exists...", projectName)
projectExists, err := api.CheckProject(projectName)
if err != nil {
return fmt.Errorf("failed to find project: %v ", utils.ParseHarborError(err))
}
if !projectExists {
return fmt.Errorf("Project %s does not exist", projectName)
}

log.Debugf("Fetching logs for project: %s", projectName)
resp, err = api.LogsProject(projectName)
if err != nil {
log.Fatalf("failed to get project logs: %v", err)
return
return fmt.Errorf("failed to get project logs: %v", err)
}

FormatFlag := viper.GetString("output-format")
if FormatFlag != "" {
err = utils.PrintFormat(resp, FormatFlag)
formatFlag := viper.GetString("output-format")
if formatFlag != "" {
log.WithField("output_format", formatFlag).Debug("Output format selected")
err = utils.PrintFormat(resp, formatFlag)
if err != nil {
log.Error(err)
return err
}
} else {
log.Debug("Listing project logs using default view")
auditLog.LogsProject(resp.Payload)
}
return nil
},
}

Expand Down
Loading