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",