From 9a33fa77d52c5d038ef300fb3ac3d8f0381d2a3d Mon Sep 17 00:00:00 2001 From: Nishchay Rajput Date: Fri, 3 Apr 2026 12:28:56 +0530 Subject: [PATCH 1/9] feat: added jobservice queue subcommand Signed-off-by: Nishchay Rajput --- cmd/harbor/root/cmd.go | 5 + cmd/harbor/root/jobservice/cmd.go | 37 ++ cmd/harbor/root/jobservice/queues/list.go | 334 ++++++++++++++++++ .../root/jobservice/queues/list_test.go | 111 ++++++ doc/cli-docs/harbor-jobservice-queues-list.md | 42 +++ .../harbor-jobservice-queues-pause.md | 46 +++ .../harbor-jobservice-queues-resume.md | 46 +++ doc/cli-docs/harbor-jobservice-queues-stop.md | 46 +++ doc/cli-docs/harbor-jobservice-queues.md | 36 ++ doc/cli-docs/harbor-jobservice.md | 36 ++ doc/cli-docs/harbor.md | 1 + .../man1/harbor-jobservice-queues-list.1 | 41 +++ .../man1/harbor-jobservice-queues-pause.1 | 51 +++ .../man1/harbor-jobservice-queues-resume.1 | 51 +++ .../man1/harbor-jobservice-queues-stop.1 | 51 +++ doc/man-docs/man1/harbor-jobservice-queues.1 | 35 ++ doc/man-docs/man1/harbor-jobservice.1 | 39 ++ doc/man-docs/man1/harbor.1 | 2 +- pkg/api/jobservice_handler.go | 52 +++ pkg/views/jobservice/queues/view.go | 40 +++ 20 files changed, 1101 insertions(+), 1 deletion(-) create mode 100644 cmd/harbor/root/jobservice/cmd.go create mode 100644 cmd/harbor/root/jobservice/queues/list.go create mode 100644 cmd/harbor/root/jobservice/queues/list_test.go create mode 100644 doc/cli-docs/harbor-jobservice-queues-list.md create mode 100644 doc/cli-docs/harbor-jobservice-queues-pause.md create mode 100644 doc/cli-docs/harbor-jobservice-queues-resume.md create mode 100644 doc/cli-docs/harbor-jobservice-queues-stop.md create mode 100644 doc/cli-docs/harbor-jobservice-queues.md create mode 100644 doc/cli-docs/harbor-jobservice.md create mode 100644 doc/man-docs/man1/harbor-jobservice-queues-list.1 create mode 100644 doc/man-docs/man1/harbor-jobservice-queues-pause.1 create mode 100644 doc/man-docs/man1/harbor-jobservice-queues-resume.1 create mode 100644 doc/man-docs/man1/harbor-jobservice-queues-stop.1 create mode 100644 doc/man-docs/man1/harbor-jobservice-queues.1 create mode 100644 doc/man-docs/man1/harbor-jobservice.1 create mode 100644 pkg/api/jobservice_handler.go create mode 100644 pkg/views/jobservice/queues/view.go diff --git a/cmd/harbor/root/cmd.go b/cmd/harbor/root/cmd.go index 039e39ea6..630c75b52 100644 --- a/cmd/harbor/root/cmd.go +++ b/cmd/harbor/root/cmd.go @@ -23,6 +23,7 @@ import ( "github.com/goharbor/harbor-cli/cmd/harbor/root/context" "github.com/goharbor/harbor-cli/cmd/harbor/root/cve" "github.com/goharbor/harbor-cli/cmd/harbor/root/instance" + "github.com/goharbor/harbor-cli/cmd/harbor/root/jobservice" "github.com/goharbor/harbor-cli/cmd/harbor/root/labels" "github.com/goharbor/harbor-cli/cmd/harbor/root/ldap" "github.com/goharbor/harbor-cli/cmd/harbor/root/project" @@ -203,6 +204,10 @@ harbor help cmd.GroupID = "system" root.AddCommand(cmd) + cmd = jobservice.JobService() + cmd.GroupID = "system" + root.AddCommand(cmd) + // Utils cmd = versionCommand() cmd.GroupID = "utils" diff --git a/cmd/harbor/root/jobservice/cmd.go b/cmd/harbor/root/jobservice/cmd.go new file mode 100644 index 000000000..00d4ecf7b --- /dev/null +++ b/cmd/harbor/root/jobservice/cmd.go @@ -0,0 +1,37 @@ +// Copyright Project Harbor Authors +// +// 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 jobservice + +import ( + "github.com/goharbor/harbor-cli/cmd/harbor/root/jobservice/queues" + "github.com/spf13/cobra" +) + +// JobService creates the jobservice command +func JobService() *cobra.Command { + cmd := &cobra.Command{ + Use: "jobservice", + Short: "Manage Harbor job service (admin only)", + Long: `Manage Harbor job service components including worker pools, job queues, schedules, and job logs. +This requires system admin privileges. + +Use "harbor jobservice [command] --help" for detailed examples and flags per subcommand.`, + } + + cmd.AddCommand( + queues.QueuesCommand(), + ) + + return cmd +} diff --git a/cmd/harbor/root/jobservice/queues/list.go b/cmd/harbor/root/jobservice/queues/list.go new file mode 100644 index 000000000..d43235c77 --- /dev/null +++ b/cmd/harbor/root/jobservice/queues/list.go @@ -0,0 +1,334 @@ +// Copyright Project Harbor Authors +// +// 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 queues + +import ( + "errors" + "fmt" + "strings" + + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/huh" + "github.com/goharbor/harbor-cli/pkg/api" + "github.com/goharbor/harbor-cli/pkg/utils" + "github.com/goharbor/harbor-cli/pkg/views/jobservice/queues" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// QueuesCommand creates the queues subcommand +func QueuesCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "queues", + Short: "Manage job queues (list, stop, pause, resume)", + Long: "List job queues and perform actions on them (stop/pause/resume).", + } + + cmd.AddCommand(ListCommand(), StopCommand(), PauseCommand(), ResumeCommand()) + + return cmd +} + +// ListCommand lists all job queues +func ListCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "List all job queues", + Long: "Display all job queues with their pending job counts and latency.", + Example: "harbor jobservice queues list", + RunE: func(cmd *cobra.Command, args []string) error { + response, err := api.ListJobQueues() + if err != nil { + return fmt.Errorf("failed to retrieve job queues: %w", err) + } + + if response == nil || response.Payload == nil || len(response.Payload) == 0 { + fmt.Println("No job queues found.") + return nil + } + + formatFlag := viper.GetString("output-format") + if formatFlag != "" { + return utils.PrintFormat(response.Payload, formatFlag) + } + + queues.ListQueues(response.Payload) + return nil + }, + } + + return cmd +} + +// StopCommand stops a job queue +func StopCommand() *cobra.Command { + var jobTypes []string + var interactive bool + + cmd := &cobra.Command{ + Use: "stop", + Short: "Stop queue(s) (--type or --interactive)", + Long: "Stop a job queue or all queues.", + Example: "harbor jobservice queues stop --type REPLICATION\nharbor jobservice queues stop --type REPLICATION --type RETENTION\nharbor jobservice queues stop --type all", + RunE: func(cmd *cobra.Command, args []string) error { + if len(jobTypes) == 0 && !interactive { + interactive = true + } + + if interactive { + selectedTypes, err := selectQueueTypes("stop") + if err != nil { + return err + } + jobTypes = selectedTypes + } + + if len(jobTypes) == 0 { + return fmt.Errorf("at least one job type must be specified with --type or interactive mode") + } + + return executeQueueAction("stop", jobTypes) + }, + } + + flags := cmd.Flags() + flags.StringSliceVar(&jobTypes, "type", nil, "Job type(s) to stop (repeat flag or comma-separate values; use 'all' for all queues)") + flags.BoolVarP(&interactive, "interactive", "i", false, "Interactive mode to choose queue type(s) instead of passing --type") + + return cmd +} + +// PauseCommand pauses a job queue +func PauseCommand() *cobra.Command { + var jobTypes []string + var interactive bool + + cmd := &cobra.Command{ + Use: "pause", + Short: "Pause queue(s) (--type or --interactive)", + Long: "Pause a job queue or all queues.", + Example: "harbor jobservice queues pause --type REPLICATION\nharbor jobservice queues pause --type REPLICATION --type RETENTION\nharbor jobservice queues pause --type all", + RunE: func(cmd *cobra.Command, args []string) error { + if len(jobTypes) == 0 && !interactive { + interactive = true + } + + if interactive { + selectedTypes, err := selectQueueTypes("pause") + if err != nil { + return err + } + jobTypes = selectedTypes + } + + if len(jobTypes) == 0 { + return fmt.Errorf("at least one job type must be specified with --type or interactive mode") + } + + return executeQueueAction("pause", jobTypes) + }, + } + + flags := cmd.Flags() + flags.StringSliceVar(&jobTypes, "type", nil, "Job type(s) to pause (repeat flag or comma-separate values; use 'all' for all queues)") + flags.BoolVarP(&interactive, "interactive", "i", false, "Interactive mode to choose queue type(s) instead of passing --type") + + return cmd +} + +// ResumeCommand resumes a job queue +func ResumeCommand() *cobra.Command { + var jobTypes []string + var interactive bool + + cmd := &cobra.Command{ + Use: "resume", + Short: "Resume queue(s) (--type or --interactive)", + Long: "Resume a paused job queue or all queues.", + Example: "harbor jobservice queues resume --type REPLICATION\nharbor jobservice queues resume --type REPLICATION --type RETENTION\nharbor jobservice queues resume --type all", + RunE: func(cmd *cobra.Command, args []string) error { + if len(jobTypes) == 0 && !interactive { + interactive = true + } + + if interactive { + selectedTypes, err := selectQueueTypes("resume") + if err != nil { + return err + } + jobTypes = selectedTypes + } + + if len(jobTypes) == 0 { + return fmt.Errorf("at least one job type must be specified with --type or interactive mode") + } + + return executeQueueAction("resume", jobTypes) + }, + } + + flags := cmd.Flags() + flags.StringSliceVar(&jobTypes, "type", nil, "Job type(s) to resume (repeat flag or comma-separate values; use 'all' for all queues)") + flags.BoolVarP(&interactive, "interactive", "i", false, "Interactive mode to choose queue type(s) instead of passing --type") + + return cmd +} + +// selectQueueTypes shows an interactive multi-selector for queue types +func selectQueueTypes(action string) ([]string, error) { + response, err := api.ListJobQueues() + if err != nil { + return nil, fmt.Errorf("failed to retrieve job queues: %w", err) + } + + if response == nil || response.Payload == nil || len(response.Payload) == 0 { + return nil, fmt.Errorf("no job queues available") + } + + filteredQueues := make([]*struct { + JobType string + Count int64 + }, 0, len(response.Payload)) + + for _, queue := range response.Payload { + if queue == nil { + continue + } + if shouldIncludeQueueForAction(action, queue.Paused) { + filteredQueues = append(filteredQueues, &struct { + JobType string + Count int64 + }{ + JobType: queue.JobType, + Count: queue.Count, + }) + } + } + + if len(filteredQueues) == 0 { + switch action { + case "resume": + return nil, fmt.Errorf("no paused queues available to resume") + case "pause": + return nil, fmt.Errorf("all queues are already paused") + default: + return nil, fmt.Errorf("no job queues available to %s", action) + } + } + + options := make([]huh.Option[string], len(filteredQueues)+1) + options[0] = huh.NewOption("all", "all") + + for i, queue := range filteredQueues { + label := fmt.Sprintf("%s (pending: %d)", queue.JobType, queue.Count) + options[i+1] = huh.NewOption(label, queue.JobType) + } + + var selected []string + theme := huh.ThemeCharm() + keymap := huh.NewDefaultKeyMap() + keymap.Quit = key.NewBinding( + key.WithKeys("ctrl+c", "q"), + key.WithHelp("q", "quit"), + ) + + err = huh.NewForm( + huh.NewGroup( + huh.NewMultiSelect[string](). + Title(fmt.Sprintf("Select queue type(s) to %s (press q to cancel)", action)). + Options(options...). + Value(&selected), + ), + ).WithTheme(theme).WithKeyMap(keymap).Run() + + if err != nil { + if errors.Is(err, huh.ErrUserAborted) { + return nil, errors.New("operation cancelled") + } + return nil, err + } + + selected = normalizeJobTypes(selected) + if len(selected) == 0 { + return nil, fmt.Errorf("at least one queue type must be selected") + } + + return selected, nil +} + +func shouldIncludeQueueForAction(action string, paused bool) bool { + switch strings.ToLower(action) { + case "resume": + return paused + case "pause": + return !paused + default: + return true + } +} + +func executeQueueAction(action string, jobTypes []string) error { + normalizedTypes := normalizeJobTypes(jobTypes) + if len(normalizedTypes) == 0 { + return fmt.Errorf("at least one job type must be provided") + } + + for _, jobType := range normalizedTypes { + fmt.Printf("%s queue type '%s'...\n", actionLabel(action), jobType) + err := api.ActionJobQueue(strings.ToUpper(jobType), action) + if err != nil { + return fmt.Errorf("failed to %s queue '%s': %w", action, jobType, err) + } + fmt.Printf("✓ Queue '%s' %sd successfully.\n", jobType, action) + } + + return nil +} + +func normalizeJobTypes(jobTypes []string) []string { + cleanedTypes := make([]string, 0, len(jobTypes)) + seen := make(map[string]struct{}, len(jobTypes)) + + for _, rawType := range jobTypes { + for _, splitType := range strings.Split(rawType, ",") { + trimmedType := strings.TrimSpace(splitType) + if trimmedType == "" { + continue + } + + if strings.EqualFold(trimmedType, "all") { + return []string{"all"} + } + + key := strings.ToLower(trimmedType) + if _, exists := seen[key]; exists { + continue + } + + seen[key] = struct{}{} + cleanedTypes = append(cleanedTypes, trimmedType) + } + } + + return cleanedTypes +} + +func actionLabel(action string) string { + if action == "" { + return "Updating" + } + + lower := strings.ToLower(action) + return strings.ToUpper(lower[:1]) + lower[1:] +} diff --git a/cmd/harbor/root/jobservice/queues/list_test.go b/cmd/harbor/root/jobservice/queues/list_test.go new file mode 100644 index 000000000..8ce0c396c --- /dev/null +++ b/cmd/harbor/root/jobservice/queues/list_test.go @@ -0,0 +1,111 @@ +// Copyright Project Harbor Authors +// +// 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 queues + +import ( + "reflect" + "testing" +) + +func TestNormalizeJobTypes(t *testing.T) { + tests := []struct { + name string + in []string + want []string + }{ + { + name: "deduplicates and trims", + in: []string{"REPLICATION", " REPLICATION ", "RETENTION"}, + want: []string{"REPLICATION", "RETENTION"}, + }, + { + name: "supports comma separated", + in: []string{"REPLICATION,RETENTION", "GC"}, + want: []string{"REPLICATION", "RETENTION", "GC"}, + }, + { + name: "all short circuits", + in: []string{"REPLICATION", "all", "RETENTION"}, + want: []string{"all"}, + }, + { + name: "case insensitive dedupe", + in: []string{"Replication", "replication", "RETENTION"}, + want: []string{"Replication", "RETENTION"}, + }, + { + name: "empty values removed", + in: []string{"", " ", ",", "REPLICATION"}, + want: []string{"REPLICATION"}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := normalizeJobTypes(tc.in) + if !reflect.DeepEqual(got, tc.want) { + t.Fatalf("normalizeJobTypes() = %v, want %v", got, tc.want) + } + }) + } +} + +func TestShouldIncludeQueueForAction(t *testing.T) { + tests := []struct { + name string + action string + paused bool + want bool + }{ + {name: "resume paused", action: "resume", paused: true, want: true}, + {name: "resume unpaused", action: "resume", paused: false, want: false}, + {name: "pause paused", action: "pause", paused: true, want: false}, + {name: "pause unpaused", action: "pause", paused: false, want: true}, + {name: "stop paused", action: "stop", paused: true, want: true}, + {name: "stop unpaused", action: "stop", paused: false, want: true}, + {name: "unknown action", action: "unknown", paused: false, want: true}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := shouldIncludeQueueForAction(tc.action, tc.paused) + if got != tc.want { + t.Fatalf("shouldIncludeQueueForAction(%q, %v) = %v, want %v", tc.action, tc.paused, got, tc.want) + } + }) + } +} + +func TestActionLabel(t *testing.T) { + tests := []struct { + name string + in string + want string + }{ + {name: "empty", in: "", want: "Updating"}, + {name: "lower", in: "pause", want: "Pause"}, + {name: "upper", in: "RESUME", want: "Resume"}, + {name: "mixed", in: "sToP", want: "Stop"}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := actionLabel(tc.in) + if got != tc.want { + t.Fatalf("actionLabel(%q) = %q, want %q", tc.in, got, tc.want) + } + }) + } +} diff --git a/doc/cli-docs/harbor-jobservice-queues-list.md b/doc/cli-docs/harbor-jobservice-queues-list.md new file mode 100644 index 000000000..e4ab35fe1 --- /dev/null +++ b/doc/cli-docs/harbor-jobservice-queues-list.md @@ -0,0 +1,42 @@ +--- +title: harbor jobservice queues list +weight: 65 +--- +## harbor jobservice queues list + +### Description + +##### List all job queues + +### Synopsis + +Display all job queues with their pending job counts and latency. + +```sh +harbor jobservice queues list [flags] +``` + +### Examples + +```sh +harbor jobservice queues list +``` + +### Options + +```sh + -h, --help help for list +``` + +### Options inherited from parent commands + +```sh + -c, --config string config file (default is $HOME/.config/harbor-cli/config.yaml) + -o, --output-format string Output format. One of: json|yaml + -v, --verbose verbose output +``` + +### SEE ALSO + +* [harbor jobservice queues](harbor-jobservice-queues.md) - Manage job queues (list, stop, pause, resume) + diff --git a/doc/cli-docs/harbor-jobservice-queues-pause.md b/doc/cli-docs/harbor-jobservice-queues-pause.md new file mode 100644 index 000000000..ad897b841 --- /dev/null +++ b/doc/cli-docs/harbor-jobservice-queues-pause.md @@ -0,0 +1,46 @@ +--- +title: harbor jobservice queues pause +weight: 80 +--- +## harbor jobservice queues pause + +### Description + +##### Pause queue(s) (--type or --interactive) + +### Synopsis + +Pause a job queue or all queues. + +```sh +harbor jobservice queues pause [flags] +``` + +### Examples + +```sh +harbor jobservice queues pause --type REPLICATION +harbor jobservice queues pause --type REPLICATION --type RETENTION +harbor jobservice queues pause --type all +``` + +### Options + +```sh + -h, --help help for pause + -i, --interactive Interactive mode to choose queue type(s) instead of passing --type + --type strings Job type(s) to pause (repeat flag or comma-separate values; use 'all' for all queues) +``` + +### Options inherited from parent commands + +```sh + -c, --config string config file (default is $HOME/.config/harbor-cli/config.yaml) + -o, --output-format string Output format. One of: json|yaml + -v, --verbose verbose output +``` + +### SEE ALSO + +* [harbor jobservice queues](harbor-jobservice-queues.md) - Manage job queues (list, stop, pause, resume) + diff --git a/doc/cli-docs/harbor-jobservice-queues-resume.md b/doc/cli-docs/harbor-jobservice-queues-resume.md new file mode 100644 index 000000000..1df0b779e --- /dev/null +++ b/doc/cli-docs/harbor-jobservice-queues-resume.md @@ -0,0 +1,46 @@ +--- +title: harbor jobservice queues resume +weight: 90 +--- +## harbor jobservice queues resume + +### Description + +##### Resume queue(s) (--type or --interactive) + +### Synopsis + +Resume a paused job queue or all queues. + +```sh +harbor jobservice queues resume [flags] +``` + +### Examples + +```sh +harbor jobservice queues resume --type REPLICATION +harbor jobservice queues resume --type REPLICATION --type RETENTION +harbor jobservice queues resume --type all +``` + +### Options + +```sh + -h, --help help for resume + -i, --interactive Interactive mode to choose queue type(s) instead of passing --type + --type strings Job type(s) to resume (repeat flag or comma-separate values; use 'all' for all queues) +``` + +### Options inherited from parent commands + +```sh + -c, --config string config file (default is $HOME/.config/harbor-cli/config.yaml) + -o, --output-format string Output format. One of: json|yaml + -v, --verbose verbose output +``` + +### SEE ALSO + +* [harbor jobservice queues](harbor-jobservice-queues.md) - Manage job queues (list, stop, pause, resume) + diff --git a/doc/cli-docs/harbor-jobservice-queues-stop.md b/doc/cli-docs/harbor-jobservice-queues-stop.md new file mode 100644 index 000000000..1992d77fa --- /dev/null +++ b/doc/cli-docs/harbor-jobservice-queues-stop.md @@ -0,0 +1,46 @@ +--- +title: harbor jobservice queues stop +weight: 60 +--- +## harbor jobservice queues stop + +### Description + +##### Stop queue(s) (--type or --interactive) + +### Synopsis + +Stop a job queue or all queues. + +```sh +harbor jobservice queues stop [flags] +``` + +### Examples + +```sh +harbor jobservice queues stop --type REPLICATION +harbor jobservice queues stop --type REPLICATION --type RETENTION +harbor jobservice queues stop --type all +``` + +### Options + +```sh + -h, --help help for stop + -i, --interactive Interactive mode to choose queue type(s) instead of passing --type + --type strings Job type(s) to stop (repeat flag or comma-separate values; use 'all' for all queues) +``` + +### Options inherited from parent commands + +```sh + -c, --config string config file (default is $HOME/.config/harbor-cli/config.yaml) + -o, --output-format string Output format. One of: json|yaml + -v, --verbose verbose output +``` + +### SEE ALSO + +* [harbor jobservice queues](harbor-jobservice-queues.md) - Manage job queues (list, stop, pause, resume) + diff --git a/doc/cli-docs/harbor-jobservice-queues.md b/doc/cli-docs/harbor-jobservice-queues.md new file mode 100644 index 000000000..9b27f3446 --- /dev/null +++ b/doc/cli-docs/harbor-jobservice-queues.md @@ -0,0 +1,36 @@ +--- +title: harbor jobservice queues +weight: 65 +--- +## harbor jobservice queues + +### Description + +##### Manage job queues (list, stop, pause, resume) + +### Synopsis + +List job queues and perform actions on them (stop/pause/resume). + +### Options + +```sh + -h, --help help for queues +``` + +### Options inherited from parent commands + +```sh + -c, --config string config file (default is $HOME/.config/harbor-cli/config.yaml) + -o, --output-format string Output format. One of: json|yaml + -v, --verbose verbose output +``` + +### SEE ALSO + +* [harbor jobservice](harbor-jobservice.md) - Manage Harbor job service (admin only) +* [harbor jobservice queues list](harbor-jobservice-queues-list.md) - List all job queues +* [harbor jobservice queues pause](harbor-jobservice-queues-pause.md) - Pause queue(s) (--type or --interactive) +* [harbor jobservice queues resume](harbor-jobservice-queues-resume.md) - Resume queue(s) (--type or --interactive) +* [harbor jobservice queues stop](harbor-jobservice-queues-stop.md) - Stop queue(s) (--type or --interactive) + diff --git a/doc/cli-docs/harbor-jobservice.md b/doc/cli-docs/harbor-jobservice.md new file mode 100644 index 000000000..3802be7a4 --- /dev/null +++ b/doc/cli-docs/harbor-jobservice.md @@ -0,0 +1,36 @@ +--- +title: harbor jobservice +weight: 75 +--- +## harbor jobservice + +### Description + +##### Manage Harbor job service (admin only) + +### Synopsis + +Manage Harbor job service components including worker pools, job queues, schedules, and job logs. +This requires system admin privileges. + +Use "harbor jobservice [command] --help" for detailed examples and flags per subcommand. + +### Options + +```sh + -h, --help help for jobservice +``` + +### Options inherited from parent commands + +```sh + -c, --config string config file (default is $HOME/.config/harbor-cli/config.yaml) + -o, --output-format string Output format. One of: json|yaml + -v, --verbose verbose output +``` + +### SEE ALSO + +* [harbor](harbor.md) - Official Harbor CLI +* [harbor jobservice queues](harbor-jobservice-queues.md) - Manage job queues (list, stop, pause, resume) + diff --git a/doc/cli-docs/harbor.md b/doc/cli-docs/harbor.md index 5dd4d604e..ec7dd3749 100644 --- a/doc/cli-docs/harbor.md +++ b/doc/cli-docs/harbor.md @@ -42,6 +42,7 @@ harbor help * [harbor health](harbor-health.md) - Get the health status of Harbor components * [harbor info](harbor-info.md) - Display detailed Harbor system, statistics, and CLI environment information * [harbor instance](harbor-instance.md) - Manage preheat provider instances in Harbor +* [harbor jobservice](harbor-jobservice.md) - Manage Harbor job service (admin only) * [harbor label](harbor-label.md) - Manage labels in Harbor * [harbor ldap](harbor-ldap.md) - Manage ldap users and groups * [harbor login](harbor-login.md) - Log in to Harbor registry diff --git a/doc/man-docs/man1/harbor-jobservice-queues-list.1 b/doc/man-docs/man1/harbor-jobservice-queues-list.1 new file mode 100644 index 000000000..9f2b8a562 --- /dev/null +++ b/doc/man-docs/man1/harbor-jobservice-queues-list.1 @@ -0,0 +1,41 @@ +.nh +.TH "HARBOR" "1" "Harbor Community" "Harbor User Manuals" + +.SH NAME +harbor-jobservice-queues-list - List all job queues + + +.SH SYNOPSIS +\fBharbor jobservice queues list [flags]\fP + + +.SH DESCRIPTION +Display all job queues with their pending job counts and latency. + + +.SH OPTIONS +\fB-h\fP, \fB--help\fP[=false] + help for list + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +\fB-c\fP, \fB--config\fP="" + config file (default is $HOME/.config/harbor-cli/config.yaml) + +.PP +\fB-o\fP, \fB--output-format\fP="" + Output format. One of: json|yaml + +.PP +\fB-v\fP, \fB--verbose\fP[=false] + verbose output + + +.SH EXAMPLE +.EX +harbor jobservice queues list +.EE + + +.SH SEE ALSO +\fBharbor-jobservice-queues(1)\fP \ No newline at end of file diff --git a/doc/man-docs/man1/harbor-jobservice-queues-pause.1 b/doc/man-docs/man1/harbor-jobservice-queues-pause.1 new file mode 100644 index 000000000..f2822f28f --- /dev/null +++ b/doc/man-docs/man1/harbor-jobservice-queues-pause.1 @@ -0,0 +1,51 @@ +.nh +.TH "HARBOR" "1" "Harbor Community" "Harbor User Manuals" + +.SH NAME +harbor-jobservice-queues-pause - Pause queue(s) (--type or --interactive) + + +.SH SYNOPSIS +\fBharbor jobservice queues pause [flags]\fP + + +.SH DESCRIPTION +Pause a job queue or all queues. + + +.SH OPTIONS +\fB-h\fP, \fB--help\fP[=false] + help for pause + +.PP +\fB-i\fP, \fB--interactive\fP[=false] + Interactive mode to choose queue type(s) instead of passing --type + +.PP +\fB--type\fP=[] + Job type(s) to pause (repeat flag or comma-separate values; use 'all' for all queues) + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +\fB-c\fP, \fB--config\fP="" + config file (default is $HOME/.config/harbor-cli/config.yaml) + +.PP +\fB-o\fP, \fB--output-format\fP="" + Output format. One of: json|yaml + +.PP +\fB-v\fP, \fB--verbose\fP[=false] + verbose output + + +.SH EXAMPLE +.EX +harbor jobservice queues pause --type REPLICATION +harbor jobservice queues pause --type REPLICATION --type RETENTION +harbor jobservice queues pause --type all +.EE + + +.SH SEE ALSO +\fBharbor-jobservice-queues(1)\fP \ No newline at end of file diff --git a/doc/man-docs/man1/harbor-jobservice-queues-resume.1 b/doc/man-docs/man1/harbor-jobservice-queues-resume.1 new file mode 100644 index 000000000..07e9136a1 --- /dev/null +++ b/doc/man-docs/man1/harbor-jobservice-queues-resume.1 @@ -0,0 +1,51 @@ +.nh +.TH "HARBOR" "1" "Harbor Community" "Harbor User Manuals" + +.SH NAME +harbor-jobservice-queues-resume - Resume queue(s) (--type or --interactive) + + +.SH SYNOPSIS +\fBharbor jobservice queues resume [flags]\fP + + +.SH DESCRIPTION +Resume a paused job queue or all queues. + + +.SH OPTIONS +\fB-h\fP, \fB--help\fP[=false] + help for resume + +.PP +\fB-i\fP, \fB--interactive\fP[=false] + Interactive mode to choose queue type(s) instead of passing --type + +.PP +\fB--type\fP=[] + Job type(s) to resume (repeat flag or comma-separate values; use 'all' for all queues) + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +\fB-c\fP, \fB--config\fP="" + config file (default is $HOME/.config/harbor-cli/config.yaml) + +.PP +\fB-o\fP, \fB--output-format\fP="" + Output format. One of: json|yaml + +.PP +\fB-v\fP, \fB--verbose\fP[=false] + verbose output + + +.SH EXAMPLE +.EX +harbor jobservice queues resume --type REPLICATION +harbor jobservice queues resume --type REPLICATION --type RETENTION +harbor jobservice queues resume --type all +.EE + + +.SH SEE ALSO +\fBharbor-jobservice-queues(1)\fP \ No newline at end of file diff --git a/doc/man-docs/man1/harbor-jobservice-queues-stop.1 b/doc/man-docs/man1/harbor-jobservice-queues-stop.1 new file mode 100644 index 000000000..ddb3c26ae --- /dev/null +++ b/doc/man-docs/man1/harbor-jobservice-queues-stop.1 @@ -0,0 +1,51 @@ +.nh +.TH "HARBOR" "1" "Harbor Community" "Harbor User Manuals" + +.SH NAME +harbor-jobservice-queues-stop - Stop queue(s) (--type or --interactive) + + +.SH SYNOPSIS +\fBharbor jobservice queues stop [flags]\fP + + +.SH DESCRIPTION +Stop a job queue or all queues. + + +.SH OPTIONS +\fB-h\fP, \fB--help\fP[=false] + help for stop + +.PP +\fB-i\fP, \fB--interactive\fP[=false] + Interactive mode to choose queue type(s) instead of passing --type + +.PP +\fB--type\fP=[] + Job type(s) to stop (repeat flag or comma-separate values; use 'all' for all queues) + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +\fB-c\fP, \fB--config\fP="" + config file (default is $HOME/.config/harbor-cli/config.yaml) + +.PP +\fB-o\fP, \fB--output-format\fP="" + Output format. One of: json|yaml + +.PP +\fB-v\fP, \fB--verbose\fP[=false] + verbose output + + +.SH EXAMPLE +.EX +harbor jobservice queues stop --type REPLICATION +harbor jobservice queues stop --type REPLICATION --type RETENTION +harbor jobservice queues stop --type all +.EE + + +.SH SEE ALSO +\fBharbor-jobservice-queues(1)\fP \ No newline at end of file diff --git a/doc/man-docs/man1/harbor-jobservice-queues.1 b/doc/man-docs/man1/harbor-jobservice-queues.1 new file mode 100644 index 000000000..792562ea2 --- /dev/null +++ b/doc/man-docs/man1/harbor-jobservice-queues.1 @@ -0,0 +1,35 @@ +.nh +.TH "HARBOR" "1" "Harbor Community" "Harbor User Manuals" + +.SH NAME +harbor-jobservice-queues - Manage job queues (list, stop, pause, resume) + + +.SH SYNOPSIS +\fBharbor jobservice queues [flags]\fP + + +.SH DESCRIPTION +List job queues and perform actions on them (stop/pause/resume). + + +.SH OPTIONS +\fB-h\fP, \fB--help\fP[=false] + help for queues + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +\fB-c\fP, \fB--config\fP="" + config file (default is $HOME/.config/harbor-cli/config.yaml) + +.PP +\fB-o\fP, \fB--output-format\fP="" + Output format. One of: json|yaml + +.PP +\fB-v\fP, \fB--verbose\fP[=false] + verbose output + + +.SH SEE ALSO +\fBharbor-jobservice(1)\fP, \fBharbor-jobservice-queues-list(1)\fP, \fBharbor-jobservice-queues-pause(1)\fP, \fBharbor-jobservice-queues-resume(1)\fP, \fBharbor-jobservice-queues-stop(1)\fP \ No newline at end of file diff --git a/doc/man-docs/man1/harbor-jobservice.1 b/doc/man-docs/man1/harbor-jobservice.1 new file mode 100644 index 000000000..0c130a640 --- /dev/null +++ b/doc/man-docs/man1/harbor-jobservice.1 @@ -0,0 +1,39 @@ +.nh +.TH "HARBOR" "1" "Harbor Community" "Harbor User Manuals" + +.SH NAME +harbor-jobservice - Manage Harbor job service (admin only) + + +.SH SYNOPSIS +\fBharbor jobservice [flags]\fP + + +.SH DESCRIPTION +Manage Harbor job service components including worker pools, job queues, schedules, and job logs. +This requires system admin privileges. + +.PP +Use "harbor jobservice [command] --help" for detailed examples and flags per subcommand. + + +.SH OPTIONS +\fB-h\fP, \fB--help\fP[=false] + help for jobservice + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +\fB-c\fP, \fB--config\fP="" + config file (default is $HOME/.config/harbor-cli/config.yaml) + +.PP +\fB-o\fP, \fB--output-format\fP="" + Output format. One of: json|yaml + +.PP +\fB-v\fP, \fB--verbose\fP[=false] + verbose output + + +.SH SEE ALSO +\fBharbor(1)\fP, \fBharbor-jobservice-queues(1)\fP \ No newline at end of file diff --git a/doc/man-docs/man1/harbor.1 b/doc/man-docs/man1/harbor.1 index 17de0d7f8..741d8f706 100644 --- a/doc/man-docs/man1/harbor.1 +++ b/doc/man-docs/man1/harbor.1 @@ -43,4 +43,4 @@ harbor help .SH SEE ALSO -\fBharbor-artifact(1)\fP, \fBharbor-config(1)\fP, \fBharbor-context(1)\fP, \fBharbor-cve-allowlist(1)\fP, \fBharbor-health(1)\fP, \fBharbor-info(1)\fP, \fBharbor-instance(1)\fP, \fBharbor-label(1)\fP, \fBharbor-ldap(1)\fP, \fBharbor-login(1)\fP, \fBharbor-logs(1)\fP, \fBharbor-password(1)\fP, \fBharbor-project(1)\fP, \fBharbor-quota(1)\fP, \fBharbor-registry(1)\fP, \fBharbor-replication(1)\fP, \fBharbor-repo(1)\fP, \fBharbor-robot(1)\fP, \fBharbor-scan-all(1)\fP, \fBharbor-scanner(1)\fP, \fBharbor-schedule(1)\fP, \fBharbor-tag(1)\fP, \fBharbor-user(1)\fP, \fBharbor-version(1)\fP, \fBharbor-vulnerability(1)\fP, \fBharbor-webhook(1)\fP \ No newline at end of file +\fBharbor-artifact(1)\fP, \fBharbor-config(1)\fP, \fBharbor-context(1)\fP, \fBharbor-cve-allowlist(1)\fP, \fBharbor-health(1)\fP, \fBharbor-info(1)\fP, \fBharbor-instance(1)\fP, \fBharbor-jobservice(1)\fP, \fBharbor-label(1)\fP, \fBharbor-ldap(1)\fP, \fBharbor-login(1)\fP, \fBharbor-logs(1)\fP, \fBharbor-password(1)\fP, \fBharbor-project(1)\fP, \fBharbor-quota(1)\fP, \fBharbor-registry(1)\fP, \fBharbor-replication(1)\fP, \fBharbor-repo(1)\fP, \fBharbor-robot(1)\fP, \fBharbor-scan-all(1)\fP, \fBharbor-scanner(1)\fP, \fBharbor-schedule(1)\fP, \fBharbor-tag(1)\fP, \fBharbor-user(1)\fP, \fBharbor-version(1)\fP, \fBharbor-vulnerability(1)\fP, \fBharbor-webhook(1)\fP \ No newline at end of file diff --git a/pkg/api/jobservice_handler.go b/pkg/api/jobservice_handler.go new file mode 100644 index 000000000..205b399ff --- /dev/null +++ b/pkg/api/jobservice_handler.go @@ -0,0 +1,52 @@ +// Copyright Project Harbor Authors +// +// 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 api + +import ( + "github.com/goharbor/go-client/pkg/sdk/v2.0/client/jobservice" + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" + "github.com/goharbor/harbor-cli/pkg/utils" +) + +// ListJobQueues retrieves all job queues +func ListJobQueues() (*jobservice.ListJobQueuesOK, error) { + ctx, client, err := utils.ContextWithClient() + if err != nil { + return nil, err + } + + response, err := client.Jobservice.ListJobQueues(ctx, &jobservice.ListJobQueuesParams{}) + if err != nil { + return nil, err + } + + return response, nil +} + +// ActionJobQueue performs an action on a job queue (stop/pause/resume) +func ActionJobQueue(jobType, action string) error { + ctx, client, err := utils.ContextWithClient() + if err != nil { + return err + } + + _, err = client.Jobservice.ActionPendingJobs(ctx, &jobservice.ActionPendingJobsParams{ + JobType: jobType, + ActionRequest: &models.ActionRequest{ + Action: action, + }, + }) + + return err +} diff --git a/pkg/views/jobservice/queues/view.go b/pkg/views/jobservice/queues/view.go new file mode 100644 index 000000000..1f4ae8344 --- /dev/null +++ b/pkg/views/jobservice/queues/view.go @@ -0,0 +1,40 @@ +// Copyright Project Harbor Authors +// +// 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 queues + +import ( + "fmt" + + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" +) + +// ListQueues displays job queues in a formatted table. +func ListQueues(items []*models.JobQueue) { + if len(items) == 0 { + fmt.Println("No job queues found.") + return + } + + fmt.Printf("%-25s %-12s %-12s %-10s\n", "JOB_TYPE", "COUNT", "LATENCY(s)", "PAUSED") + fmt.Printf("%-25s %-12s %-12s %-10s\n", "--------", "-----", "----------", "------") + + for _, queue := range items { + if queue == nil { + continue + } + fmt.Printf("%-25s %-12d %-12d %-10t\n", queue.JobType, queue.Count, queue.Latency, queue.Paused) + } + + fmt.Printf("\nTotal: %d queue(s)\n", len(items)) +} From 62e8dda599c863f940e94d9ccbb62982bacf281f Mon Sep 17 00:00:00 2001 From: Nishchay Rajput Date: Sat, 4 Apr 2026 14:16:19 +0530 Subject: [PATCH 2/9] moved: subcommand to seperate files Signed-off-by: Nishchay Rajput --- cmd/harbor/root/jobservice/queues/cmd.go | 29 ++ cmd/harbor/root/jobservice/queues/list.go | 284 +----------------- .../root/jobservice/queues/list_test.go | 111 ------- cmd/harbor/root/jobservice/queues/pause.go | 59 ++++ cmd/harbor/root/jobservice/queues/resume.go | 58 ++++ cmd/harbor/root/jobservice/queues/stop.go | 59 ++++ cmd/harbor/root/jobservice/queues/utils.go | 172 +++++++++++ 7 files changed, 379 insertions(+), 393 deletions(-) create mode 100644 cmd/harbor/root/jobservice/queues/cmd.go delete mode 100644 cmd/harbor/root/jobservice/queues/list_test.go create mode 100644 cmd/harbor/root/jobservice/queues/pause.go create mode 100644 cmd/harbor/root/jobservice/queues/resume.go create mode 100644 cmd/harbor/root/jobservice/queues/stop.go create mode 100644 cmd/harbor/root/jobservice/queues/utils.go diff --git a/cmd/harbor/root/jobservice/queues/cmd.go b/cmd/harbor/root/jobservice/queues/cmd.go new file mode 100644 index 000000000..0450d6b47 --- /dev/null +++ b/cmd/harbor/root/jobservice/queues/cmd.go @@ -0,0 +1,29 @@ +// Copyright Project Harbor Authors +// +// 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 queues + +import "github.com/spf13/cobra" + +// QueuesCommand creates the queues subcommand +func QueuesCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "queues", + Short: "Manage job queues (list, stop, pause, resume)", + Long: "List job queues and perform actions on them (stop/pause/resume).", + } + + cmd.AddCommand(ListCommand(), StopCommand(), PauseCommand(), ResumeCommand()) + + return cmd +} diff --git a/cmd/harbor/root/jobservice/queues/list.go b/cmd/harbor/root/jobservice/queues/list.go index d43235c77..8a4405b2e 100644 --- a/cmd/harbor/root/jobservice/queues/list.go +++ b/cmd/harbor/root/jobservice/queues/list.go @@ -14,32 +14,14 @@ package queues import ( - "errors" "fmt" - "strings" - - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/huh" "github.com/goharbor/harbor-cli/pkg/api" "github.com/goharbor/harbor-cli/pkg/utils" - "github.com/goharbor/harbor-cli/pkg/views/jobservice/queues" + queuesview "github.com/goharbor/harbor-cli/pkg/views/jobservice/queues" "github.com/spf13/cobra" "github.com/spf13/viper" ) -// QueuesCommand creates the queues subcommand -func QueuesCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "queues", - Short: "Manage job queues (list, stop, pause, resume)", - Long: "List job queues and perform actions on them (stop/pause/resume).", - } - - cmd.AddCommand(ListCommand(), StopCommand(), PauseCommand(), ResumeCommand()) - - return cmd -} - // ListCommand lists all job queues func ListCommand() *cobra.Command { cmd := &cobra.Command{ @@ -63,272 +45,10 @@ func ListCommand() *cobra.Command { return utils.PrintFormat(response.Payload, formatFlag) } - queues.ListQueues(response.Payload) + queuesview.ListQueues(response.Payload) return nil }, } return cmd } - -// StopCommand stops a job queue -func StopCommand() *cobra.Command { - var jobTypes []string - var interactive bool - - cmd := &cobra.Command{ - Use: "stop", - Short: "Stop queue(s) (--type or --interactive)", - Long: "Stop a job queue or all queues.", - Example: "harbor jobservice queues stop --type REPLICATION\nharbor jobservice queues stop --type REPLICATION --type RETENTION\nharbor jobservice queues stop --type all", - RunE: func(cmd *cobra.Command, args []string) error { - if len(jobTypes) == 0 && !interactive { - interactive = true - } - - if interactive { - selectedTypes, err := selectQueueTypes("stop") - if err != nil { - return err - } - jobTypes = selectedTypes - } - - if len(jobTypes) == 0 { - return fmt.Errorf("at least one job type must be specified with --type or interactive mode") - } - - return executeQueueAction("stop", jobTypes) - }, - } - - flags := cmd.Flags() - flags.StringSliceVar(&jobTypes, "type", nil, "Job type(s) to stop (repeat flag or comma-separate values; use 'all' for all queues)") - flags.BoolVarP(&interactive, "interactive", "i", false, "Interactive mode to choose queue type(s) instead of passing --type") - - return cmd -} - -// PauseCommand pauses a job queue -func PauseCommand() *cobra.Command { - var jobTypes []string - var interactive bool - - cmd := &cobra.Command{ - Use: "pause", - Short: "Pause queue(s) (--type or --interactive)", - Long: "Pause a job queue or all queues.", - Example: "harbor jobservice queues pause --type REPLICATION\nharbor jobservice queues pause --type REPLICATION --type RETENTION\nharbor jobservice queues pause --type all", - RunE: func(cmd *cobra.Command, args []string) error { - if len(jobTypes) == 0 && !interactive { - interactive = true - } - - if interactive { - selectedTypes, err := selectQueueTypes("pause") - if err != nil { - return err - } - jobTypes = selectedTypes - } - - if len(jobTypes) == 0 { - return fmt.Errorf("at least one job type must be specified with --type or interactive mode") - } - - return executeQueueAction("pause", jobTypes) - }, - } - - flags := cmd.Flags() - flags.StringSliceVar(&jobTypes, "type", nil, "Job type(s) to pause (repeat flag or comma-separate values; use 'all' for all queues)") - flags.BoolVarP(&interactive, "interactive", "i", false, "Interactive mode to choose queue type(s) instead of passing --type") - - return cmd -} - -// ResumeCommand resumes a job queue -func ResumeCommand() *cobra.Command { - var jobTypes []string - var interactive bool - - cmd := &cobra.Command{ - Use: "resume", - Short: "Resume queue(s) (--type or --interactive)", - Long: "Resume a paused job queue or all queues.", - Example: "harbor jobservice queues resume --type REPLICATION\nharbor jobservice queues resume --type REPLICATION --type RETENTION\nharbor jobservice queues resume --type all", - RunE: func(cmd *cobra.Command, args []string) error { - if len(jobTypes) == 0 && !interactive { - interactive = true - } - - if interactive { - selectedTypes, err := selectQueueTypes("resume") - if err != nil { - return err - } - jobTypes = selectedTypes - } - - if len(jobTypes) == 0 { - return fmt.Errorf("at least one job type must be specified with --type or interactive mode") - } - - return executeQueueAction("resume", jobTypes) - }, - } - - flags := cmd.Flags() - flags.StringSliceVar(&jobTypes, "type", nil, "Job type(s) to resume (repeat flag or comma-separate values; use 'all' for all queues)") - flags.BoolVarP(&interactive, "interactive", "i", false, "Interactive mode to choose queue type(s) instead of passing --type") - - return cmd -} - -// selectQueueTypes shows an interactive multi-selector for queue types -func selectQueueTypes(action string) ([]string, error) { - response, err := api.ListJobQueues() - if err != nil { - return nil, fmt.Errorf("failed to retrieve job queues: %w", err) - } - - if response == nil || response.Payload == nil || len(response.Payload) == 0 { - return nil, fmt.Errorf("no job queues available") - } - - filteredQueues := make([]*struct { - JobType string - Count int64 - }, 0, len(response.Payload)) - - for _, queue := range response.Payload { - if queue == nil { - continue - } - if shouldIncludeQueueForAction(action, queue.Paused) { - filteredQueues = append(filteredQueues, &struct { - JobType string - Count int64 - }{ - JobType: queue.JobType, - Count: queue.Count, - }) - } - } - - if len(filteredQueues) == 0 { - switch action { - case "resume": - return nil, fmt.Errorf("no paused queues available to resume") - case "pause": - return nil, fmt.Errorf("all queues are already paused") - default: - return nil, fmt.Errorf("no job queues available to %s", action) - } - } - - options := make([]huh.Option[string], len(filteredQueues)+1) - options[0] = huh.NewOption("all", "all") - - for i, queue := range filteredQueues { - label := fmt.Sprintf("%s (pending: %d)", queue.JobType, queue.Count) - options[i+1] = huh.NewOption(label, queue.JobType) - } - - var selected []string - theme := huh.ThemeCharm() - keymap := huh.NewDefaultKeyMap() - keymap.Quit = key.NewBinding( - key.WithKeys("ctrl+c", "q"), - key.WithHelp("q", "quit"), - ) - - err = huh.NewForm( - huh.NewGroup( - huh.NewMultiSelect[string](). - Title(fmt.Sprintf("Select queue type(s) to %s (press q to cancel)", action)). - Options(options...). - Value(&selected), - ), - ).WithTheme(theme).WithKeyMap(keymap).Run() - - if err != nil { - if errors.Is(err, huh.ErrUserAborted) { - return nil, errors.New("operation cancelled") - } - return nil, err - } - - selected = normalizeJobTypes(selected) - if len(selected) == 0 { - return nil, fmt.Errorf("at least one queue type must be selected") - } - - return selected, nil -} - -func shouldIncludeQueueForAction(action string, paused bool) bool { - switch strings.ToLower(action) { - case "resume": - return paused - case "pause": - return !paused - default: - return true - } -} - -func executeQueueAction(action string, jobTypes []string) error { - normalizedTypes := normalizeJobTypes(jobTypes) - if len(normalizedTypes) == 0 { - return fmt.Errorf("at least one job type must be provided") - } - - for _, jobType := range normalizedTypes { - fmt.Printf("%s queue type '%s'...\n", actionLabel(action), jobType) - err := api.ActionJobQueue(strings.ToUpper(jobType), action) - if err != nil { - return fmt.Errorf("failed to %s queue '%s': %w", action, jobType, err) - } - fmt.Printf("✓ Queue '%s' %sd successfully.\n", jobType, action) - } - - return nil -} - -func normalizeJobTypes(jobTypes []string) []string { - cleanedTypes := make([]string, 0, len(jobTypes)) - seen := make(map[string]struct{}, len(jobTypes)) - - for _, rawType := range jobTypes { - for _, splitType := range strings.Split(rawType, ",") { - trimmedType := strings.TrimSpace(splitType) - if trimmedType == "" { - continue - } - - if strings.EqualFold(trimmedType, "all") { - return []string{"all"} - } - - key := strings.ToLower(trimmedType) - if _, exists := seen[key]; exists { - continue - } - - seen[key] = struct{}{} - cleanedTypes = append(cleanedTypes, trimmedType) - } - } - - return cleanedTypes -} - -func actionLabel(action string) string { - if action == "" { - return "Updating" - } - - lower := strings.ToLower(action) - return strings.ToUpper(lower[:1]) + lower[1:] -} diff --git a/cmd/harbor/root/jobservice/queues/list_test.go b/cmd/harbor/root/jobservice/queues/list_test.go deleted file mode 100644 index 8ce0c396c..000000000 --- a/cmd/harbor/root/jobservice/queues/list_test.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright Project Harbor Authors -// -// 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 queues - -import ( - "reflect" - "testing" -) - -func TestNormalizeJobTypes(t *testing.T) { - tests := []struct { - name string - in []string - want []string - }{ - { - name: "deduplicates and trims", - in: []string{"REPLICATION", " REPLICATION ", "RETENTION"}, - want: []string{"REPLICATION", "RETENTION"}, - }, - { - name: "supports comma separated", - in: []string{"REPLICATION,RETENTION", "GC"}, - want: []string{"REPLICATION", "RETENTION", "GC"}, - }, - { - name: "all short circuits", - in: []string{"REPLICATION", "all", "RETENTION"}, - want: []string{"all"}, - }, - { - name: "case insensitive dedupe", - in: []string{"Replication", "replication", "RETENTION"}, - want: []string{"Replication", "RETENTION"}, - }, - { - name: "empty values removed", - in: []string{"", " ", ",", "REPLICATION"}, - want: []string{"REPLICATION"}, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - got := normalizeJobTypes(tc.in) - if !reflect.DeepEqual(got, tc.want) { - t.Fatalf("normalizeJobTypes() = %v, want %v", got, tc.want) - } - }) - } -} - -func TestShouldIncludeQueueForAction(t *testing.T) { - tests := []struct { - name string - action string - paused bool - want bool - }{ - {name: "resume paused", action: "resume", paused: true, want: true}, - {name: "resume unpaused", action: "resume", paused: false, want: false}, - {name: "pause paused", action: "pause", paused: true, want: false}, - {name: "pause unpaused", action: "pause", paused: false, want: true}, - {name: "stop paused", action: "stop", paused: true, want: true}, - {name: "stop unpaused", action: "stop", paused: false, want: true}, - {name: "unknown action", action: "unknown", paused: false, want: true}, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - got := shouldIncludeQueueForAction(tc.action, tc.paused) - if got != tc.want { - t.Fatalf("shouldIncludeQueueForAction(%q, %v) = %v, want %v", tc.action, tc.paused, got, tc.want) - } - }) - } -} - -func TestActionLabel(t *testing.T) { - tests := []struct { - name string - in string - want string - }{ - {name: "empty", in: "", want: "Updating"}, - {name: "lower", in: "pause", want: "Pause"}, - {name: "upper", in: "RESUME", want: "Resume"}, - {name: "mixed", in: "sToP", want: "Stop"}, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - got := actionLabel(tc.in) - if got != tc.want { - t.Fatalf("actionLabel(%q) = %q, want %q", tc.in, got, tc.want) - } - }) - } -} diff --git a/cmd/harbor/root/jobservice/queues/pause.go b/cmd/harbor/root/jobservice/queues/pause.go new file mode 100644 index 000000000..03d28ec41 --- /dev/null +++ b/cmd/harbor/root/jobservice/queues/pause.go @@ -0,0 +1,59 @@ +// Copyright Project Harbor Authors +// +// 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 queues + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// PauseCommand pauses a job queue +func PauseCommand() *cobra.Command { + var jobTypes []string + var interactive bool + + cmd := &cobra.Command{ + Use: "pause", + Short: "Pause queue(s) (--type or --interactive)", + Long: "Pause a job queue or all queues.", + Example: "harbor jobservice queues pause --type REPLICATION\nharbor jobservice queues pause --type REPLICATION --type RETENTION\nharbor jobservice queues pause --type all", + RunE: func(cmd *cobra.Command, args []string) error { + if len(jobTypes) == 0 && !interactive { + interactive = true + } + + if interactive { + selectedTypes, err := selectQueueTypes("pause") + if err != nil { + return err + } + jobTypes = selectedTypes + } + + if len(jobTypes) == 0 { + return fmt.Errorf("at least one job type must be specified with --type or interactive mode") + } + + return executeQueueAction("pause", jobTypes) + }, + } + + flags := cmd.Flags() + flags.StringSliceVar(&jobTypes, "type", nil, "Job type(s) to pause (repeat flag or comma-separate values; use 'all' for all queues)") + flags.BoolVarP(&interactive, "interactive", "i", false, "Interactive mode to choose queue type(s) instead of passing --type") + + return cmd +} \ No newline at end of file diff --git a/cmd/harbor/root/jobservice/queues/resume.go b/cmd/harbor/root/jobservice/queues/resume.go new file mode 100644 index 000000000..181bdf5ff --- /dev/null +++ b/cmd/harbor/root/jobservice/queues/resume.go @@ -0,0 +1,58 @@ +// Copyright Project Harbor Authors +// +// 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 queues + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// ResumeCommand resumes a job queue +func ResumeCommand() *cobra.Command { + var jobTypes []string + var interactive bool + + cmd := &cobra.Command{ + Use: "resume", + Short: "Resume queue(s) (--type or --interactive)", + Long: "Resume a paused job queue or all queues.", + Example: "harbor jobservice queues resume --type REPLICATION\nharbor jobservice queues resume --type REPLICATION --type RETENTION\nharbor jobservice queues resume --type all", + RunE: func(cmd *cobra.Command, args []string) error { + if len(jobTypes) == 0 && !interactive { + interactive = true + } + + if interactive { + selectedTypes, err := selectQueueTypes("resume") + if err != nil { + return err + } + jobTypes = selectedTypes + } + + if len(jobTypes) == 0 { + return fmt.Errorf("at least one job type must be specified with --type or interactive mode") + } + + return executeQueueAction("resume", jobTypes) + }, + } + + flags := cmd.Flags() + flags.StringSliceVar(&jobTypes, "type", nil, "Job type(s) to resume (repeat flag or comma-separate values; use 'all' for all queues)") + flags.BoolVarP(&interactive, "interactive", "i", false, "Interactive mode to choose queue type(s) instead of passing --type") + + return cmd +} \ No newline at end of file diff --git a/cmd/harbor/root/jobservice/queues/stop.go b/cmd/harbor/root/jobservice/queues/stop.go new file mode 100644 index 000000000..1c06df316 --- /dev/null +++ b/cmd/harbor/root/jobservice/queues/stop.go @@ -0,0 +1,59 @@ +// Copyright Project Harbor Authors +// +// 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 queues + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// StopCommand stops a job queue +func StopCommand() *cobra.Command { + var jobTypes []string + var interactive bool + + cmd := &cobra.Command{ + Use: "stop", + Short: "Stop queue(s) (--type or --interactive)", + Long: "Stop a job queue or all queues.", + Example: "harbor jobservice queues stop --type REPLICATION\nharbor jobservice queues stop --type REPLICATION --type RETENTION\nharbor jobservice queues stop --type all", + RunE: func(cmd *cobra.Command, args []string) error { + if len(jobTypes) == 0 && !interactive { + interactive = true + } + + if interactive { + selectedTypes, err := selectQueueTypes("stop") + if err != nil { + return err + } + jobTypes = selectedTypes + } + + if len(jobTypes) == 0 { + return fmt.Errorf("at least one job type must be specified with --type or interactive mode") + } + + return executeQueueAction("stop", jobTypes) + }, + } + + flags := cmd.Flags() + flags.StringSliceVar(&jobTypes, "type", nil, "Job type(s) to stop (repeat flag or comma-separate values; use 'all' for all queues)") + flags.BoolVarP(&interactive, "interactive", "i", false, "Interactive mode to choose queue type(s) instead of passing --type") + + return cmd +} \ No newline at end of file diff --git a/cmd/harbor/root/jobservice/queues/utils.go b/cmd/harbor/root/jobservice/queues/utils.go new file mode 100644 index 000000000..55b39e80d --- /dev/null +++ b/cmd/harbor/root/jobservice/queues/utils.go @@ -0,0 +1,172 @@ +// Copyright Project Harbor Authors +// +// 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 queues + +import ( + "errors" + "fmt" + "strings" + + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/huh" + "github.com/goharbor/harbor-cli/pkg/api" +) + +func shouldIncludeQueueForAction(action string, paused bool) bool { + switch strings.ToLower(action) { + case "resume": + return paused + case "pause": + return !paused + default: + return true + } +} + +func executeQueueAction(action string, jobTypes []string) error { + normalizedTypes := normalizeJobTypes(jobTypes) + if len(normalizedTypes) == 0 { + return fmt.Errorf("at least one job type must be provided") + } + + for _, jobType := range normalizedTypes { + fmt.Printf("%s queue type '%s'...\n", actionLabel(action), jobType) + err := api.ActionJobQueue(strings.ToUpper(jobType), action) + if err != nil { + return fmt.Errorf("failed to %s queue '%s': %w", action, jobType, err) + } + fmt.Printf("✓ Queue '%s' %sd successfully.\n", jobType, action) + } + + return nil +} + +func normalizeJobTypes(jobTypes []string) []string { + cleanedTypes := make([]string, 0, len(jobTypes)) + seen := make(map[string]struct{}, len(jobTypes)) + + for _, rawType := range jobTypes { + for _, splitType := range strings.Split(rawType, ",") { + trimmedType := strings.TrimSpace(splitType) + if trimmedType == "" { + continue + } + + if strings.EqualFold(trimmedType, "all") { + return []string{"all"} + } + + key := strings.ToLower(trimmedType) + if _, exists := seen[key]; exists { + continue + } + + seen[key] = struct{}{} + cleanedTypes = append(cleanedTypes, trimmedType) + } + } + + return cleanedTypes +} + +func actionLabel(action string) string { + if action == "" { + return "Updating" + } + + lower := strings.ToLower(action) + return strings.ToUpper(lower[:1]) + lower[1:] +} + +// selectQueueTypes shows an interactive multi-selector for queue types +func selectQueueTypes(action string) ([]string, error) { + response, err := api.ListJobQueues() + if err != nil { + return nil, fmt.Errorf("failed to retrieve job queues: %w", err) + } + + if response == nil || response.Payload == nil || len(response.Payload) == 0 { + return nil, fmt.Errorf("no job queues available") + } + + filteredQueues := make([]*struct { + JobType string + Count int64 + }, 0, len(response.Payload)) + + for _, queue := range response.Payload { + if queue == nil { + continue + } + if shouldIncludeQueueForAction(action, queue.Paused) { + filteredQueues = append(filteredQueues, &struct { + JobType string + Count int64 + }{ + JobType: queue.JobType, + Count: queue.Count, + }) + } + } + + if len(filteredQueues) == 0 { + switch action { + case "resume": + return nil, fmt.Errorf("no paused queues available to resume") + case "pause": + return nil, fmt.Errorf("all queues are already paused") + default: + return nil, fmt.Errorf("no job queues available to %s", action) + } + } + + options := make([]huh.Option[string], len(filteredQueues)+1) + options[0] = huh.NewOption("all", "all") + + for i, queue := range filteredQueues { + label := fmt.Sprintf("%s (pending: %d)", queue.JobType, queue.Count) + options[i+1] = huh.NewOption(label, queue.JobType) + } + + var selected []string + theme := huh.ThemeCharm() + keymap := huh.NewDefaultKeyMap() + keymap.Quit = key.NewBinding( + key.WithKeys("ctrl+c", "q"), + key.WithHelp("q", "quit"), + ) + + err = huh.NewForm( + huh.NewGroup( + huh.NewMultiSelect[string](). + Title(fmt.Sprintf("Select queue type(s) to %s (press q to cancel)", action)). + Options(options...). + Value(&selected), + ), + ).WithTheme(theme).WithKeyMap(keymap).Run() + + if err != nil { + if errors.Is(err, huh.ErrUserAborted) { + return nil, errors.New("operation cancelled") + } + return nil, err + } + + selected = normalizeJobTypes(selected) + if len(selected) == 0 { + return nil, fmt.Errorf("at least one queue type must be selected") + } + + return selected, nil +} From 796e0cc2e05bf67d2b023f1dbe3d3d46a9e1ee32 Mon Sep 17 00:00:00 2001 From: Nishchay Rajput Date: Tue, 7 Apr 2026 21:39:10 +0530 Subject: [PATCH 3/9] fix: lint Signed-off-by: Nishchay Rajput --- cmd/harbor/root/jobservice/queues/pause.go | 2 +- cmd/harbor/root/jobservice/queues/resume.go | 2 +- cmd/harbor/root/jobservice/queues/stop.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/harbor/root/jobservice/queues/pause.go b/cmd/harbor/root/jobservice/queues/pause.go index 03d28ec41..35dfc118c 100644 --- a/cmd/harbor/root/jobservice/queues/pause.go +++ b/cmd/harbor/root/jobservice/queues/pause.go @@ -56,4 +56,4 @@ func PauseCommand() *cobra.Command { flags.BoolVarP(&interactive, "interactive", "i", false, "Interactive mode to choose queue type(s) instead of passing --type") return cmd -} \ No newline at end of file +} diff --git a/cmd/harbor/root/jobservice/queues/resume.go b/cmd/harbor/root/jobservice/queues/resume.go index 181bdf5ff..ec898d0c8 100644 --- a/cmd/harbor/root/jobservice/queues/resume.go +++ b/cmd/harbor/root/jobservice/queues/resume.go @@ -55,4 +55,4 @@ func ResumeCommand() *cobra.Command { flags.BoolVarP(&interactive, "interactive", "i", false, "Interactive mode to choose queue type(s) instead of passing --type") return cmd -} \ No newline at end of file +} diff --git a/cmd/harbor/root/jobservice/queues/stop.go b/cmd/harbor/root/jobservice/queues/stop.go index 1c06df316..6d755c6cd 100644 --- a/cmd/harbor/root/jobservice/queues/stop.go +++ b/cmd/harbor/root/jobservice/queues/stop.go @@ -56,4 +56,4 @@ func StopCommand() *cobra.Command { flags.BoolVarP(&interactive, "interactive", "i", false, "Interactive mode to choose queue type(s) instead of passing --type") return cmd -} \ No newline at end of file +} From 288bbb9b3be275ef078f048d5d94096f4159ddf1 Mon Sep 17 00:00:00 2001 From: Nishchay Rajput Date: Tue, 7 Apr 2026 22:44:05 +0530 Subject: [PATCH 4/9] fix: lints error and improve error messages Signed-off-by: Nishchay Rajput --- cmd/harbor/root/jobservice/queues/list.go | 4 +- cmd/harbor/root/jobservice/queues/utils.go | 9 +++- pkg/utils/jobservice/errors.go | 48 ++++++++++++++++++++++ 3 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 pkg/utils/jobservice/errors.go diff --git a/cmd/harbor/root/jobservice/queues/list.go b/cmd/harbor/root/jobservice/queues/list.go index 8a4405b2e..144b3f9de 100644 --- a/cmd/harbor/root/jobservice/queues/list.go +++ b/cmd/harbor/root/jobservice/queues/list.go @@ -15,8 +15,10 @@ package queues import ( "fmt" + "github.com/goharbor/harbor-cli/pkg/api" "github.com/goharbor/harbor-cli/pkg/utils" + jobserviceutils "github.com/goharbor/harbor-cli/pkg/utils/jobservice" queuesview "github.com/goharbor/harbor-cli/pkg/views/jobservice/queues" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -32,7 +34,7 @@ func ListCommand() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { response, err := api.ListJobQueues() if err != nil { - return fmt.Errorf("failed to retrieve job queues: %w", err) + return jobserviceutils.FormatScheduleError("failed to retrieve job queues", err, "read") } if response == nil || response.Payload == nil || len(response.Payload) == 0 { diff --git a/cmd/harbor/root/jobservice/queues/utils.go b/cmd/harbor/root/jobservice/queues/utils.go index 55b39e80d..3ed217071 100644 --- a/cmd/harbor/root/jobservice/queues/utils.go +++ b/cmd/harbor/root/jobservice/queues/utils.go @@ -21,6 +21,7 @@ import ( "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/huh" "github.com/goharbor/harbor-cli/pkg/api" + jobserviceutils "github.com/goharbor/harbor-cli/pkg/utils/jobservice" ) func shouldIncludeQueueForAction(action string, paused bool) bool { @@ -44,7 +45,11 @@ func executeQueueAction(action string, jobTypes []string) error { fmt.Printf("%s queue type '%s'...\n", actionLabel(action), jobType) err := api.ActionJobQueue(strings.ToUpper(jobType), action) if err != nil { - return fmt.Errorf("failed to %s queue '%s': %w", action, jobType, err) + return jobserviceutils.FormatScheduleError( + fmt.Sprintf("failed to %s queue '%s'", action, jobType), + err, + "update", + ) } fmt.Printf("✓ Queue '%s' %sd successfully.\n", jobType, action) } @@ -93,7 +98,7 @@ func actionLabel(action string) string { func selectQueueTypes(action string) ([]string, error) { response, err := api.ListJobQueues() if err != nil { - return nil, fmt.Errorf("failed to retrieve job queues: %w", err) + return nil, jobserviceutils.FormatScheduleError("failed to retrieve job queues", err, "read") } if response == nil || response.Payload == nil || len(response.Payload) == 0 { diff --git a/pkg/utils/jobservice/errors.go b/pkg/utils/jobservice/errors.go new file mode 100644 index 000000000..a7c222ee0 --- /dev/null +++ b/pkg/utils/jobservice/errors.go @@ -0,0 +1,48 @@ +// Copyright Project Harbor Authors +// +// 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 jobservice + +import ( + "fmt" + + "github.com/goharbor/harbor-cli/pkg/utils" +) + +func FormatScheduleError(operation string, err error, requiredPermission string) error { + errorCode := utils.ParseHarborErrorCode(err) + + switch errorCode { + case "400": + return fmt.Errorf("%s: invalid request. For schedule status use job_type=all; for queue action use stop|pause|resume", operation) + case "401": + return fmt.Errorf("%s: authentication required. Please run 'harbor login' and try again", operation) + case "403": + if requiredPermission == "authenticated" { + return fmt.Errorf("%s: permission denied. Your account is authenticated but lacks access", operation) + } + return fmt.Errorf("%s: permission denied. This operation requires %s on jobservice-monitor", operation, requiredPermission) + case "404": + return fmt.Errorf("%s: resource not found or not accessible in current context", operation) + case "422": + return fmt.Errorf("%s: request validation failed. Please check request body and action values", operation) + case "500": + return fmt.Errorf("%s: Harbor internal error. Retry and check Harbor server logs", operation) + default: + msg := utils.ParseHarborErrorMsg(err) + if msg == "" { + msg = err.Error() + } + return fmt.Errorf("%s: %s", operation, msg) + } +} From c21c375a58dbbc501051e7096ba998020e89a913 Mon Sep 17 00:00:00 2001 From: Nishchay Rajput Date: Tue, 14 Apr 2026 19:45:38 +0530 Subject: [PATCH 5/9] list test Signed-off-by: Nishchay Rajput --- .../root/jobservice/queues/list_test.go | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 cmd/harbor/root/jobservice/queues/list_test.go diff --git a/cmd/harbor/root/jobservice/queues/list_test.go b/cmd/harbor/root/jobservice/queues/list_test.go new file mode 100644 index 000000000..8ce0c396c --- /dev/null +++ b/cmd/harbor/root/jobservice/queues/list_test.go @@ -0,0 +1,111 @@ +// Copyright Project Harbor Authors +// +// 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 queues + +import ( + "reflect" + "testing" +) + +func TestNormalizeJobTypes(t *testing.T) { + tests := []struct { + name string + in []string + want []string + }{ + { + name: "deduplicates and trims", + in: []string{"REPLICATION", " REPLICATION ", "RETENTION"}, + want: []string{"REPLICATION", "RETENTION"}, + }, + { + name: "supports comma separated", + in: []string{"REPLICATION,RETENTION", "GC"}, + want: []string{"REPLICATION", "RETENTION", "GC"}, + }, + { + name: "all short circuits", + in: []string{"REPLICATION", "all", "RETENTION"}, + want: []string{"all"}, + }, + { + name: "case insensitive dedupe", + in: []string{"Replication", "replication", "RETENTION"}, + want: []string{"Replication", "RETENTION"}, + }, + { + name: "empty values removed", + in: []string{"", " ", ",", "REPLICATION"}, + want: []string{"REPLICATION"}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := normalizeJobTypes(tc.in) + if !reflect.DeepEqual(got, tc.want) { + t.Fatalf("normalizeJobTypes() = %v, want %v", got, tc.want) + } + }) + } +} + +func TestShouldIncludeQueueForAction(t *testing.T) { + tests := []struct { + name string + action string + paused bool + want bool + }{ + {name: "resume paused", action: "resume", paused: true, want: true}, + {name: "resume unpaused", action: "resume", paused: false, want: false}, + {name: "pause paused", action: "pause", paused: true, want: false}, + {name: "pause unpaused", action: "pause", paused: false, want: true}, + {name: "stop paused", action: "stop", paused: true, want: true}, + {name: "stop unpaused", action: "stop", paused: false, want: true}, + {name: "unknown action", action: "unknown", paused: false, want: true}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := shouldIncludeQueueForAction(tc.action, tc.paused) + if got != tc.want { + t.Fatalf("shouldIncludeQueueForAction(%q, %v) = %v, want %v", tc.action, tc.paused, got, tc.want) + } + }) + } +} + +func TestActionLabel(t *testing.T) { + tests := []struct { + name string + in string + want string + }{ + {name: "empty", in: "", want: "Updating"}, + {name: "lower", in: "pause", want: "Pause"}, + {name: "upper", in: "RESUME", want: "Resume"}, + {name: "mixed", in: "sToP", want: "Stop"}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := actionLabel(tc.in) + if got != tc.want { + t.Fatalf("actionLabel(%q) = %q, want %q", tc.in, got, tc.want) + } + }) + } +} From 00744f05ecf931cc877daec4b46f46de17942d43 Mon Sep 17 00:00:00 2001 From: Nishchay Rajput Date: Sat, 4 Apr 2026 14:16:19 +0530 Subject: [PATCH 6/9] moved: subcommand to seperate files Signed-off-by: Nishchay Rajput --- .../root/jobservice/queues/list_test.go | 111 ------------------ cmd/harbor/root/jobservice/queues/utils.go | 3 + 2 files changed, 3 insertions(+), 111 deletions(-) delete mode 100644 cmd/harbor/root/jobservice/queues/list_test.go diff --git a/cmd/harbor/root/jobservice/queues/list_test.go b/cmd/harbor/root/jobservice/queues/list_test.go deleted file mode 100644 index 8ce0c396c..000000000 --- a/cmd/harbor/root/jobservice/queues/list_test.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright Project Harbor Authors -// -// 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 queues - -import ( - "reflect" - "testing" -) - -func TestNormalizeJobTypes(t *testing.T) { - tests := []struct { - name string - in []string - want []string - }{ - { - name: "deduplicates and trims", - in: []string{"REPLICATION", " REPLICATION ", "RETENTION"}, - want: []string{"REPLICATION", "RETENTION"}, - }, - { - name: "supports comma separated", - in: []string{"REPLICATION,RETENTION", "GC"}, - want: []string{"REPLICATION", "RETENTION", "GC"}, - }, - { - name: "all short circuits", - in: []string{"REPLICATION", "all", "RETENTION"}, - want: []string{"all"}, - }, - { - name: "case insensitive dedupe", - in: []string{"Replication", "replication", "RETENTION"}, - want: []string{"Replication", "RETENTION"}, - }, - { - name: "empty values removed", - in: []string{"", " ", ",", "REPLICATION"}, - want: []string{"REPLICATION"}, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - got := normalizeJobTypes(tc.in) - if !reflect.DeepEqual(got, tc.want) { - t.Fatalf("normalizeJobTypes() = %v, want %v", got, tc.want) - } - }) - } -} - -func TestShouldIncludeQueueForAction(t *testing.T) { - tests := []struct { - name string - action string - paused bool - want bool - }{ - {name: "resume paused", action: "resume", paused: true, want: true}, - {name: "resume unpaused", action: "resume", paused: false, want: false}, - {name: "pause paused", action: "pause", paused: true, want: false}, - {name: "pause unpaused", action: "pause", paused: false, want: true}, - {name: "stop paused", action: "stop", paused: true, want: true}, - {name: "stop unpaused", action: "stop", paused: false, want: true}, - {name: "unknown action", action: "unknown", paused: false, want: true}, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - got := shouldIncludeQueueForAction(tc.action, tc.paused) - if got != tc.want { - t.Fatalf("shouldIncludeQueueForAction(%q, %v) = %v, want %v", tc.action, tc.paused, got, tc.want) - } - }) - } -} - -func TestActionLabel(t *testing.T) { - tests := []struct { - name string - in string - want string - }{ - {name: "empty", in: "", want: "Updating"}, - {name: "lower", in: "pause", want: "Pause"}, - {name: "upper", in: "RESUME", want: "Resume"}, - {name: "mixed", in: "sToP", want: "Stop"}, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - got := actionLabel(tc.in) - if got != tc.want { - t.Fatalf("actionLabel(%q) = %q, want %q", tc.in, got, tc.want) - } - }) - } -} diff --git a/cmd/harbor/root/jobservice/queues/utils.go b/cmd/harbor/root/jobservice/queues/utils.go index 3ed217071..228f6bf55 100644 --- a/cmd/harbor/root/jobservice/queues/utils.go +++ b/cmd/harbor/root/jobservice/queues/utils.go @@ -21,7 +21,10 @@ import ( "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/huh" "github.com/goharbor/harbor-cli/pkg/api" +<<<<<<< HEAD jobserviceutils "github.com/goharbor/harbor-cli/pkg/utils/jobservice" +======= +>>>>>>> 803208e (moved: subcommand to seperate files) ) func shouldIncludeQueueForAction(action string, paused bool) bool { From ff8aa83a9b1f219880b5d5f9ec01092cd0b50c8c Mon Sep 17 00:00:00 2001 From: Nishchay Rajput Date: Tue, 7 Apr 2026 22:44:05 +0530 Subject: [PATCH 7/9] fix: reabasing with main Signed-off-by: Nishchay Rajput --- cmd/harbor/root/jobservice/queues/utils.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/harbor/root/jobservice/queues/utils.go b/cmd/harbor/root/jobservice/queues/utils.go index 228f6bf55..32a072e1f 100644 --- a/cmd/harbor/root/jobservice/queues/utils.go +++ b/cmd/harbor/root/jobservice/queues/utils.go @@ -21,10 +21,14 @@ import ( "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/huh" "github.com/goharbor/harbor-cli/pkg/api" +<<<<<<< HEAD <<<<<<< HEAD jobserviceutils "github.com/goharbor/harbor-cli/pkg/utils/jobservice" ======= >>>>>>> 803208e (moved: subcommand to seperate files) +======= + jobserviceutils "github.com/goharbor/harbor-cli/pkg/utils/jobservice" +>>>>>>> 80eb00c (fix: lints error and improve error messages) ) func shouldIncludeQueueForAction(action string, paused bool) bool { From b48e6d1e60ab7c1d32155fefe9adee65b54f20aa Mon Sep 17 00:00:00 2001 From: Nishchay Rajput Date: Wed, 15 Apr 2026 01:57:59 +0530 Subject: [PATCH 8/9] fix: stale conflicted code Signed-off-by: Nishchay Rajput --- cmd/harbor/root/jobservice/queues/utils.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/cmd/harbor/root/jobservice/queues/utils.go b/cmd/harbor/root/jobservice/queues/utils.go index 32a072e1f..3ed217071 100644 --- a/cmd/harbor/root/jobservice/queues/utils.go +++ b/cmd/harbor/root/jobservice/queues/utils.go @@ -21,14 +21,7 @@ import ( "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/huh" "github.com/goharbor/harbor-cli/pkg/api" -<<<<<<< HEAD -<<<<<<< HEAD jobserviceutils "github.com/goharbor/harbor-cli/pkg/utils/jobservice" -======= ->>>>>>> 803208e (moved: subcommand to seperate files) -======= - jobserviceutils "github.com/goharbor/harbor-cli/pkg/utils/jobservice" ->>>>>>> 80eb00c (fix: lints error and improve error messages) ) func shouldIncludeQueueForAction(action string, paused bool) bool { From c30ef5125ec0f7ab403984cf8ca8f77bcae79074 Mon Sep 17 00:00:00 2001 From: Nishchay Rajput Date: Fri, 1 May 2026 16:53:45 +0530 Subject: [PATCH 9/9] fix: docs and lint Signed-off-by: Nishchay Rajput --- doc/cli-docs/harbor-jobservice-queues-list.md | 2 +- doc/cli-docs/harbor-jobservice-queues-pause.md | 2 +- doc/cli-docs/harbor-jobservice-queues-resume.md | 2 +- doc/cli-docs/harbor-jobservice-queues-stop.md | 2 +- doc/cli-docs/harbor-jobservice-queues.md | 2 +- doc/cli-docs/harbor-jobservice.md | 2 +- doc/man-docs/man1/harbor-jobservice-queues-list.1 | 2 +- doc/man-docs/man1/harbor-jobservice-queues-pause.1 | 2 +- doc/man-docs/man1/harbor-jobservice-queues-resume.1 | 2 +- doc/man-docs/man1/harbor-jobservice-queues-stop.1 | 2 +- doc/man-docs/man1/harbor-jobservice-queues.1 | 2 +- doc/man-docs/man1/harbor-jobservice.1 | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/doc/cli-docs/harbor-jobservice-queues-list.md b/doc/cli-docs/harbor-jobservice-queues-list.md index e4ab35fe1..82af3ee30 100644 --- a/doc/cli-docs/harbor-jobservice-queues-list.md +++ b/doc/cli-docs/harbor-jobservice-queues-list.md @@ -32,7 +32,7 @@ harbor jobservice queues list ```sh -c, --config string config file (default is $HOME/.config/harbor-cli/config.yaml) - -o, --output-format string Output format. One of: json|yaml + -o, --output-format string Output format. One of: json|yaml|csv -v, --verbose verbose output ``` diff --git a/doc/cli-docs/harbor-jobservice-queues-pause.md b/doc/cli-docs/harbor-jobservice-queues-pause.md index ad897b841..bc19335f1 100644 --- a/doc/cli-docs/harbor-jobservice-queues-pause.md +++ b/doc/cli-docs/harbor-jobservice-queues-pause.md @@ -36,7 +36,7 @@ harbor jobservice queues pause --type all ```sh -c, --config string config file (default is $HOME/.config/harbor-cli/config.yaml) - -o, --output-format string Output format. One of: json|yaml + -o, --output-format string Output format. One of: json|yaml|csv -v, --verbose verbose output ``` diff --git a/doc/cli-docs/harbor-jobservice-queues-resume.md b/doc/cli-docs/harbor-jobservice-queues-resume.md index 1df0b779e..0042c2154 100644 --- a/doc/cli-docs/harbor-jobservice-queues-resume.md +++ b/doc/cli-docs/harbor-jobservice-queues-resume.md @@ -36,7 +36,7 @@ harbor jobservice queues resume --type all ```sh -c, --config string config file (default is $HOME/.config/harbor-cli/config.yaml) - -o, --output-format string Output format. One of: json|yaml + -o, --output-format string Output format. One of: json|yaml|csv -v, --verbose verbose output ``` diff --git a/doc/cli-docs/harbor-jobservice-queues-stop.md b/doc/cli-docs/harbor-jobservice-queues-stop.md index 1992d77fa..6d9b100b2 100644 --- a/doc/cli-docs/harbor-jobservice-queues-stop.md +++ b/doc/cli-docs/harbor-jobservice-queues-stop.md @@ -36,7 +36,7 @@ harbor jobservice queues stop --type all ```sh -c, --config string config file (default is $HOME/.config/harbor-cli/config.yaml) - -o, --output-format string Output format. One of: json|yaml + -o, --output-format string Output format. One of: json|yaml|csv -v, --verbose verbose output ``` diff --git a/doc/cli-docs/harbor-jobservice-queues.md b/doc/cli-docs/harbor-jobservice-queues.md index 9b27f3446..970fd89b2 100644 --- a/doc/cli-docs/harbor-jobservice-queues.md +++ b/doc/cli-docs/harbor-jobservice-queues.md @@ -22,7 +22,7 @@ List job queues and perform actions on them (stop/pause/resume). ```sh -c, --config string config file (default is $HOME/.config/harbor-cli/config.yaml) - -o, --output-format string Output format. One of: json|yaml + -o, --output-format string Output format. One of: json|yaml|csv -v, --verbose verbose output ``` diff --git a/doc/cli-docs/harbor-jobservice.md b/doc/cli-docs/harbor-jobservice.md index 3802be7a4..f5d076328 100644 --- a/doc/cli-docs/harbor-jobservice.md +++ b/doc/cli-docs/harbor-jobservice.md @@ -25,7 +25,7 @@ Use "harbor jobservice [command] --help" for detailed examples and flags per sub ```sh -c, --config string config file (default is $HOME/.config/harbor-cli/config.yaml) - -o, --output-format string Output format. One of: json|yaml + -o, --output-format string Output format. One of: json|yaml|csv -v, --verbose verbose output ``` diff --git a/doc/man-docs/man1/harbor-jobservice-queues-list.1 b/doc/man-docs/man1/harbor-jobservice-queues-list.1 index 9f2b8a562..ac23a09fd 100644 --- a/doc/man-docs/man1/harbor-jobservice-queues-list.1 +++ b/doc/man-docs/man1/harbor-jobservice-queues-list.1 @@ -24,7 +24,7 @@ Display all job queues with their pending job counts and latency. .PP \fB-o\fP, \fB--output-format\fP="" - Output format. One of: json|yaml + Output format. One of: json|yaml|csv .PP \fB-v\fP, \fB--verbose\fP[=false] diff --git a/doc/man-docs/man1/harbor-jobservice-queues-pause.1 b/doc/man-docs/man1/harbor-jobservice-queues-pause.1 index f2822f28f..37eaeb574 100644 --- a/doc/man-docs/man1/harbor-jobservice-queues-pause.1 +++ b/doc/man-docs/man1/harbor-jobservice-queues-pause.1 @@ -32,7 +32,7 @@ Pause a job queue or all queues. .PP \fB-o\fP, \fB--output-format\fP="" - Output format. One of: json|yaml + Output format. One of: json|yaml|csv .PP \fB-v\fP, \fB--verbose\fP[=false] diff --git a/doc/man-docs/man1/harbor-jobservice-queues-resume.1 b/doc/man-docs/man1/harbor-jobservice-queues-resume.1 index 07e9136a1..4a4fbb427 100644 --- a/doc/man-docs/man1/harbor-jobservice-queues-resume.1 +++ b/doc/man-docs/man1/harbor-jobservice-queues-resume.1 @@ -32,7 +32,7 @@ Resume a paused job queue or all queues. .PP \fB-o\fP, \fB--output-format\fP="" - Output format. One of: json|yaml + Output format. One of: json|yaml|csv .PP \fB-v\fP, \fB--verbose\fP[=false] diff --git a/doc/man-docs/man1/harbor-jobservice-queues-stop.1 b/doc/man-docs/man1/harbor-jobservice-queues-stop.1 index ddb3c26ae..dad7e1808 100644 --- a/doc/man-docs/man1/harbor-jobservice-queues-stop.1 +++ b/doc/man-docs/man1/harbor-jobservice-queues-stop.1 @@ -32,7 +32,7 @@ Stop a job queue or all queues. .PP \fB-o\fP, \fB--output-format\fP="" - Output format. One of: json|yaml + Output format. One of: json|yaml|csv .PP \fB-v\fP, \fB--verbose\fP[=false] diff --git a/doc/man-docs/man1/harbor-jobservice-queues.1 b/doc/man-docs/man1/harbor-jobservice-queues.1 index 792562ea2..754312b37 100644 --- a/doc/man-docs/man1/harbor-jobservice-queues.1 +++ b/doc/man-docs/man1/harbor-jobservice-queues.1 @@ -24,7 +24,7 @@ List job queues and perform actions on them (stop/pause/resume). .PP \fB-o\fP, \fB--output-format\fP="" - Output format. One of: json|yaml + Output format. One of: json|yaml|csv .PP \fB-v\fP, \fB--verbose\fP[=false] diff --git a/doc/man-docs/man1/harbor-jobservice.1 b/doc/man-docs/man1/harbor-jobservice.1 index 0c130a640..fb479f926 100644 --- a/doc/man-docs/man1/harbor-jobservice.1 +++ b/doc/man-docs/man1/harbor-jobservice.1 @@ -28,7 +28,7 @@ Use "harbor jobservice [command] --help" for detailed examples and flags per sub .PP \fB-o\fP, \fB--output-format\fP="" - Output format. One of: json|yaml + Output format. One of: json|yaml|csv .PP \fB-v\fP, \fB--verbose\fP[=false]