diff --git a/docs/user_docs/cli/cli.md b/docs/user_docs/cli/cli.md
index 47358e2e8..f9a6bd916 100644
--- a/docs/user_docs/cli/cli.md
+++ b/docs/user_docs/cli/cli.md
@@ -32,6 +32,7 @@ Manage alert receiver, include add, list and delete receiver.
BackupRepo command.
* [kbcli backuprepo create](kbcli_backuprepo_create.md) - Create a backup repo
+* [kbcli backuprepo delete](kbcli_backuprepo_delete.md) - Delete a backup repository.
* [kbcli backuprepo describe](kbcli_backuprepo_describe.md) - Describe a backup repository.
* [kbcli backuprepo list](kbcli_backuprepo_list.md) - List Backup Repositories.
* [kbcli backuprepo update](kbcli_backuprepo_update.md) - Update a backup repository.
diff --git a/docs/user_docs/cli/kbcli_backuprepo.md b/docs/user_docs/cli/kbcli_backuprepo.md
index 9e336ad45..bfaacb01a 100644
--- a/docs/user_docs/cli/kbcli_backuprepo.md
+++ b/docs/user_docs/cli/kbcli_backuprepo.md
@@ -38,6 +38,7 @@ BackupRepo command.
* [kbcli backuprepo create](kbcli_backuprepo_create.md) - Create a backup repo
+* [kbcli backuprepo delete](kbcli_backuprepo_delete.md) - Delete a backup repository.
* [kbcli backuprepo describe](kbcli_backuprepo_describe.md) - Describe a backup repository.
* [kbcli backuprepo list](kbcli_backuprepo_list.md) - List Backup Repositories.
* [kbcli backuprepo update](kbcli_backuprepo_update.md) - Update a backup repository.
diff --git a/pkg/cluster/charts/apecloud-mysql-cluster.tgz b/pkg/cluster/charts/apecloud-mysql-cluster.tgz
index 136f764a3..a4be41c62 100644
Binary files a/pkg/cluster/charts/apecloud-mysql-cluster.tgz and b/pkg/cluster/charts/apecloud-mysql-cluster.tgz differ
diff --git a/pkg/cluster/charts/kafka-cluster.tgz b/pkg/cluster/charts/kafka-cluster.tgz
index 260b961f4..1a30fc624 100644
Binary files a/pkg/cluster/charts/kafka-cluster.tgz and b/pkg/cluster/charts/kafka-cluster.tgz differ
diff --git a/pkg/cluster/charts/llm-cluster.tgz b/pkg/cluster/charts/llm-cluster.tgz
index bfd115a93..e7822fa82 100644
Binary files a/pkg/cluster/charts/llm-cluster.tgz and b/pkg/cluster/charts/llm-cluster.tgz differ
diff --git a/pkg/cluster/charts/mongodb-cluster.tgz b/pkg/cluster/charts/mongodb-cluster.tgz
index 9235ef36a..bc5b5437c 100644
Binary files a/pkg/cluster/charts/mongodb-cluster.tgz and b/pkg/cluster/charts/mongodb-cluster.tgz differ
diff --git a/pkg/cluster/charts/postgresql-cluster.tgz b/pkg/cluster/charts/postgresql-cluster.tgz
index c3262ab08..366bd541f 100644
Binary files a/pkg/cluster/charts/postgresql-cluster.tgz and b/pkg/cluster/charts/postgresql-cluster.tgz differ
diff --git a/pkg/cluster/charts/redis-cluster.tgz b/pkg/cluster/charts/redis-cluster.tgz
index f23be98a0..6bb89370b 100644
Binary files a/pkg/cluster/charts/redis-cluster.tgz and b/pkg/cluster/charts/redis-cluster.tgz differ
diff --git a/pkg/cluster/charts/xinference-cluster.tgz b/pkg/cluster/charts/xinference-cluster.tgz
index d7502c725..438fcfbc5 100644
Binary files a/pkg/cluster/charts/xinference-cluster.tgz and b/pkg/cluster/charts/xinference-cluster.tgz differ
diff --git a/pkg/cmd/backuprepo/backuprepo.go b/pkg/cmd/backuprepo/backuprepo.go
index 593c39109..f086a07c6 100644
--- a/pkg/cmd/backuprepo/backuprepo.go
+++ b/pkg/cmd/backuprepo/backuprepo.go
@@ -35,6 +35,7 @@ func NewBackupRepoCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *co
newUpdateCommand(nil, f, streams),
newListCommand(f, streams),
newDescribeCommand(f, streams),
+ newDeleteCommand(f, streams),
)
return cmd
}
diff --git a/pkg/cmd/backuprepo/delete.go b/pkg/cmd/backuprepo/delete.go
new file mode 100644
index 000000000..47e8e4970
--- /dev/null
+++ b/pkg/cmd/backuprepo/delete.go
@@ -0,0 +1,91 @@
+/*
+Copyright (C) 2022-2023 ApeCloud Co., Ltd
+
+This file is part of KubeBlocks project
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+package backuprepo
+
+import (
+ "fmt"
+
+ "github.com/spf13/cobra"
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/cli-runtime/pkg/genericiooptions"
+ cmdutil "k8s.io/kubectl/pkg/cmd/util"
+ "k8s.io/kubectl/pkg/util/templates"
+
+ "github.com/apecloud/kbcli/pkg/action"
+ "github.com/apecloud/kbcli/pkg/types"
+ "github.com/apecloud/kbcli/pkg/util"
+ dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1"
+)
+
+var (
+ deleteExample = templates.Examples(`
+ # Delete a backuprepo
+ kbcli backuprepo delete my-backuprepo
+ `)
+)
+
+func newDeleteCommand(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
+ o := action.NewDeleteOptions(f, streams, types.BackupRepoGVR())
+ o.PreDeleteHook = preDeleteBackupRepo
+ cmd := &cobra.Command{
+ Use: "delete",
+ Short: "Delete a backup repository.",
+ Example: deleteExample,
+ ValidArgsFunction: util.ResourceNameCompletionFunc(f, types.BackupRepoGVR()),
+ Run: func(cmd *cobra.Command, args []string) {
+ cmdutil.CheckErr(completeForDeleteBackupRepo(o, args))
+ cmdutil.CheckErr(o.Run())
+ },
+ }
+ return cmd
+}
+
+func preDeleteBackupRepo(o *action.DeleteOptions, obj runtime.Object) error {
+ unstructured := obj.(*unstructured.Unstructured)
+ repo := &dpv1alpha1.BackupRepo{}
+ if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstructured.Object, repo); err != nil {
+ return err
+ }
+
+ dynamic, err := o.Factory.DynamicClient()
+ if err != nil {
+ return err
+ }
+ backupNum, _, err := countBackupNumsAndSize(dynamic, repo)
+ if err != nil {
+ return err
+ }
+ if backupNum > 0 {
+ return fmt.Errorf("this backup repository cannot be deleted because it is still containing %d backup(s)", backupNum)
+ }
+ return nil
+}
+
+func completeForDeleteBackupRepo(o *action.DeleteOptions, args []string) error {
+ if len(args) == 0 {
+ return fmt.Errorf("missing backup repository name")
+ }
+ if len(args) > 1 {
+ return fmt.Errorf("can't delete multiple backup repositories at once")
+ }
+ o.Names = args
+ return nil
+}
diff --git a/pkg/cmd/backuprepo/delete_test.go b/pkg/cmd/backuprepo/delete_test.go
new file mode 100644
index 000000000..7ddb7afb5
--- /dev/null
+++ b/pkg/cmd/backuprepo/delete_test.go
@@ -0,0 +1,129 @@
+/*
+Copyright (C) 2022-2023 ApeCloud Co., Ltd
+
+This file is part of KubeBlocks project
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+package backuprepo
+
+import (
+ "bytes"
+ "net/http"
+
+ "github.com/apecloud/kbcli/pkg/scheme"
+ "github.com/apecloud/kbcli/pkg/testing"
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/cli-runtime/pkg/genericiooptions"
+ "k8s.io/cli-runtime/pkg/resource"
+ clientfake "k8s.io/client-go/rest/fake"
+ cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
+
+ "github.com/apecloud/kbcli/pkg/action"
+ clitesting "github.com/apecloud/kbcli/pkg/testing"
+ "github.com/apecloud/kbcli/pkg/types"
+ dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1"
+)
+
+var _ = Describe("backuprepo delete command", func() {
+ var streams genericiooptions.IOStreams
+ var in *bytes.Buffer
+ var tf *cmdtesting.TestFactory
+
+ BeforeEach(func() {
+ streams, in, _, _ = genericiooptions.NewTestIOStreams()
+ tf = testing.NewTestFactory(testing.Namespace)
+ })
+
+ initClient := func(repoObj runtime.Object, otherObjs ...runtime.Object) {
+ _ = dpv1alpha1.AddToScheme(scheme.Scheme)
+ codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
+ httpResp := func(obj runtime.Object) *http.Response {
+ return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, obj)}
+ }
+
+ tf.UnstructuredClient = &clientfake.RESTClient{
+ GroupVersion: schema.GroupVersion{Group: types.DPAPIGroup, Version: types.DPAPIVersion},
+ NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
+ Client: clientfake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
+ return httpResp(repoObj), nil
+ }),
+ }
+
+ tf.FakeDynamicClient = clitesting.FakeDynamicClient(append([]runtime.Object{repoObj}, otherObjs...)...)
+ tf.Client = tf.UnstructuredClient
+ }
+
+ AfterEach(func() {
+ tf.Cleanup()
+ })
+
+ It("tests completeForDeleteBackupRepo function", func() {
+ o := action.NewDeleteOptions(tf, streams, types.OpsGVR())
+
+ By("case: it should fail if no name is specified")
+ err := completeForDeleteBackupRepo(o, []string{})
+ Expect(err).Should(HaveOccurred())
+
+ By("case: it should fail if multiple names are specified")
+ err = completeForDeleteBackupRepo(o, []string{"name1", "name2"})
+ Expect(err).Should(HaveOccurred())
+
+ By("case: it should ok")
+ err = completeForDeleteBackupRepo(o, []string{"name1"})
+ Expect(err).ShouldNot(HaveOccurred())
+ })
+
+ It("should refuse to delete if the backup repo is not clear", func() {
+ const testBackupRepo = "test-backuprepo"
+ repoObj := testing.FakeBackupRepo(testBackupRepo, false)
+ backupObj := testing.FakeBackup("test-backup")
+ backupObj.Labels = map[string]string{
+ associatedBackupRepoKey: testBackupRepo,
+ }
+ initClient(repoObj, backupObj)
+ // confirm
+ in.Write([]byte(testBackupRepo + "\n"))
+
+ o := action.NewDeleteOptions(tf, streams, types.BackupRepoGVR())
+ o.PreDeleteHook = preDeleteBackupRepo
+ err := completeForDeleteBackupRepo(o, []string{testBackupRepo})
+ Expect(err).ShouldNot(HaveOccurred())
+
+ err = o.Run()
+ Expect(err).Should(HaveOccurred())
+ Expect(err.Error()).Should(ContainSubstring("this backup repository cannot be deleted because it is still containing"))
+ })
+
+ It("should the backup repo", func() {
+ const testBackupRepo = "test-backuprepo"
+ repoObj := testing.FakeBackupRepo(testBackupRepo, false)
+ repoObj.Status.Phase = dpv1alpha1.BackupRepoFailed
+ initClient(repoObj)
+ // confirm
+ in.Write([]byte(testBackupRepo + "\n"))
+
+ o := action.NewDeleteOptions(tf, streams, types.BackupRepoGVR())
+ o.PreDeleteHook = preDeleteBackupRepo
+ err := completeForDeleteBackupRepo(o, []string{testBackupRepo})
+ Expect(err).ShouldNot(HaveOccurred())
+
+ err = o.Run()
+ Expect(err).ShouldNot(HaveOccurred())
+ })
+})
diff --git a/pkg/testing/factory.go b/pkg/testing/factory.go
index b403a56b0..5149e23b0 100644
--- a/pkg/testing/factory.go
+++ b/pkg/testing/factory.go
@@ -138,6 +138,22 @@ func testDynamicResources() []*restmapper.APIGroupResources {
},
},
},
+ {
+ Group: metav1.APIGroup{
+ Name: "dataprotection.kubeblocks.io",
+ Versions: []metav1.GroupVersionForDiscovery{
+ {GroupVersion: "dataprotection.kubeblocks.io/v1alpha1", Version: "v1alpha1"},
+ },
+ PreferredVersion: metav1.GroupVersionForDiscovery{
+ GroupVersion: "dataprotection.kubeblocks.io/v1alpha1",
+ Version: "v1alpha1"},
+ },
+ VersionedResources: map[string][]metav1.APIResource{
+ "v1alpha1": {
+ {Name: "backuprepos", Namespaced: false, Kind: "BackupRepo"},
+ },
+ },
+ },
{
Group: metav1.APIGroup{
Name: "chaos-mesh.org",