Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ The project's first goal is to reach WebUI parity.

❌ Distribution

❌ GC
✅ gc Manage Garbage Collection

❌ Job Service Dashboard

Expand Down
5 changes: 5 additions & 0 deletions cmd/harbor/root/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"github.com/goharbor/harbor-cli/cmd/harbor/root/tag"
"github.com/goharbor/harbor-cli/cmd/harbor/root/user"
"github.com/goharbor/harbor-cli/cmd/harbor/root/vulnerability"
"github.com/goharbor/harbor-cli/cmd/harbor/root/gc"
"github.com/goharbor/harbor-cli/cmd/harbor/root/webhook"
Comment thread
gcharpe1604 marked this conversation as resolved.
"github.com/goharbor/harbor-cli/pkg/utils"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -208,6 +209,10 @@ harbor help
cmd.GroupID = "system"
root.AddCommand(cmd)

cmd = gc.GC()
cmd.GroupID = "system"
root.AddCommand(cmd)

// Utils
cmd = versionCommand()
cmd.GroupID = "utils"
Expand Down
55 changes: 55 additions & 0 deletions cmd/harbor/root/gc/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// 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 gc

import "github.com/spf13/cobra"

func GC() *cobra.Command {
cmd := &cobra.Command{
Use: "gc",
Short: "Manage Garbage Collection in Harbor",
Long: `Use this command to manage registry-wide Garbage Collection (GC) in your Harbor instance.

Garbage Collection cleans up deleted or orphaned blobs/tags in the registry to free up storage space.
This command supports listing execution history, viewing logs, showing schedule configuration, stopping running jobs, and triggering manual runs.`,
Example: ` # View Garbage Collection execution history
harbor gc history

# Get the current Garbage Collection schedule
harbor gc schedule

# Trigger Garbage Collection run immediately
harbor gc trigger --delete-untagged --dry-run=false

# View execution logs for a GC run
harbor gc log 12

# Stop a running Garbage Collection run
harbor gc stop 12

# Update the automatic Garbage Collection schedule
harbor gc update-schedule daily --delete-untagged`,
}

cmd.AddCommand(
HistoryGCOperation(),
ScheduleGCOperation(),
TriggerGCOperation(),
LogGCOperation(),
StopGCOperation(),
UpdateGCScheduleCommand(),
)

return cmd
}
91 changes: 91 additions & 0 deletions cmd/harbor/root/gc/gc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// 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 gc

import (
"testing"

"github.com/goharbor/harbor-cli/pkg/testutil"
)

func TestHistoryGCOperation_Errors(t *testing.T) {
tests := []struct {
name string
flags []string
expectError bool
}{
{
name: "negative page size",
flags: []string{"--page-size", "-1"},
expectError: true,
},
{
name: "page size too large",
flags: []string{"--page-size", "101"},
expectError: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := testutil.TestCmd(t, HistoryGCOperation, tt.flags...)

if tt.expectError && err == nil {
t.Fatalf("expected error but got nil")
}

if !tt.expectError && err != nil {
t.Fatalf("unexpected error: %v", err)
}
})
}
}

func TestUpdateGCScheduleCommand_Errors(t *testing.T) {
tests := []struct {
name string
flags []string
expectError bool
}{
{
name: "invalid schedule type",
flags: []string{"invalid-type"},
expectError: true,
},
{
name: "custom type without cron",
flags: []string{"custom"},
expectError: true,
},
{
name: "custom type with invalid cron fields",
flags: []string{"custom", "--cron", "* * * * *"},
expectError: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := testutil.TestCmd(t, UpdateGCScheduleCommand, tt.flags...)

if tt.expectError && err == nil {
t.Fatalf("expected error but got nil")
}

if !tt.expectError && err != nil {
t.Fatalf("unexpected error: %v", err)
}
})
}
}
78 changes: 78 additions & 0 deletions cmd/harbor/root/gc/history.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// 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 gc

import (
"fmt"

"github.com/goharbor/harbor-cli/pkg/api"
"github.com/goharbor/harbor-cli/pkg/utils"
"github.com/goharbor/harbor-cli/pkg/views/gc/list"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

func HistoryGCOperation() *cobra.Command {
var opts api.ListFlags

cmd := &cobra.Command{
Use: "history",
Short: "Get GC execution history",
Long: `Retrieve the execution history of registry-wide Garbage Collection jobs.`,
Example: ` harbor gc history --page 1 --page-size 10`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
if opts.Page < 1 {
return fmt.Errorf("page number must be greater than or equal to 1")
}
if opts.PageSize < 0 {
return fmt.Errorf("page size must be greater than or equal to 0")
}
if opts.PageSize > 100 {
return fmt.Errorf("page size should be less than or equal to 100")
}

logrus.Debug("Fetching Garbage Collection execution history")
history, err := api.ListGCHistory(opts)
if err != nil {
return fmt.Errorf("failed to list GC history: %v", utils.ParseHarborErrorMsg(err))
}

if len(history) == 0 {
fmt.Println("No Garbage Collection execution history found")
return nil
}

formatFlag := viper.GetString("output-format")
if formatFlag != "" {
err = utils.PrintFormat(history, formatFlag)
if err != nil {
return err
}
} else {
list.ListGCHistory(history)
}
return nil
},
}

flags := cmd.Flags()
flags.Int64VarP(&opts.Page, "page", "", 1, "Page number")
flags.Int64VarP(&opts.PageSize, "page-size", "", 10, "Size of per page")
flags.StringVarP(&opts.Q, "query", "q", "", "Query string to query resources")
flags.StringVarP(&opts.Sort, "sort", "", "", "Sort the resource list in ascending or descending order")

return cmd
}
56 changes: 56 additions & 0 deletions cmd/harbor/root/gc/log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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 gc

import (
"fmt"
"strconv"

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

func LogGCOperation() *cobra.Command {
cmd := &cobra.Command{
Use: "log [gc-id]",
Short: "Get GC execution log",
Long: `Retrieve the execution log of a specific Garbage Collection run by its ID.`,
Example: ` harbor gc log 12`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
gcID, err := strconv.ParseInt(args[0], 10, 64)
if err != nil {
return fmt.Errorf("invalid GC execution ID: %v", args[0])
}

logrus.Debugf("Fetching Garbage Collection execution log for ID: %d", gcID)
logs, err := api.GetGCLog(gcID)
if err != nil {
return fmt.Errorf("failed to get GC log: %v", utils.ParseHarborErrorMsg(err))
}

if logs == "" {
fmt.Printf("No logs found for Garbage Collection run ID: %d\n", gcID)
return nil
}

fmt.Println(logs)
return nil
},
}

return cmd
}
55 changes: 55 additions & 0 deletions cmd/harbor/root/gc/schedule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// 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 gc

import (
"fmt"

"github.com/goharbor/harbor-cli/pkg/api"
"github.com/goharbor/harbor-cli/pkg/utils"
"github.com/goharbor/harbor-cli/pkg/views/gc/view"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

func ScheduleGCOperation() *cobra.Command {
cmd := &cobra.Command{
Use: "schedule",
Short: "Get the current GC schedule",
Long: `Retrieve the configuration and schedule parameters for automatic Garbage Collection.`,
Example: ` harbor gc schedule`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
logrus.Debug("Fetching Garbage Collection schedule")
schedule, err := api.GetGCSchedule()
if err != nil {
return fmt.Errorf("failed to get GC schedule: %v", utils.ParseHarborErrorMsg(err))
}

formatFlag := viper.GetString("output-format")
if formatFlag != "" {
err = utils.PrintFormat(schedule, formatFlag)
if err != nil {
return err
}
} else {
view.ViewGCSchedule(schedule)
}
return nil
},
}

return cmd
}
Loading
Loading