Skip to content

Commit a978ebc

Browse files
committed
Add Support for Restoring OCS Operator CRs in ODF CLI
This PR adds support for restoring deleted Custom Resources for OCS operator via the ODF CLI. It includes logic for handling storageclusters CRs by dynamically setting groupName and versionResource, ensuring compatibility and reliability during the restore process. Signed-off-by: Oded Viner <[email protected]>
1 parent b620cbf commit a978ebc

File tree

4 files changed

+177
-5
lines changed

4 files changed

+177
-5
lines changed

cmd/odf/restore/crds.go

+99-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,63 @@
11
package restore
22

33
import (
4+
"fmt"
5+
"strings"
6+
47
"github.com/red-hat-storage/odf-cli/cmd/odf/root"
58
"github.com/rook/kubectl-rook-ceph/pkg/k8sutil"
9+
"github.com/rook/kubectl-rook-ceph/pkg/logging"
610
pkgrestore "github.com/rook/kubectl-rook-ceph/pkg/restore"
711
"github.com/spf13/cobra"
812
)
913

14+
// groupVersions defines the supported CRD groups and their corresponding API versions.
15+
var groupVersions = map[string]string{
16+
"ocs.openshift.io": "v1",
17+
"ceph.rook.io": "v1",
18+
"csi.ceph.io": "v1beta1",
19+
"odf.openshift.io": "v1alpha1",
20+
"noobaa.io": "v1alpha1",
21+
"csiaddons.openshift.io": "v1alpha1",
22+
}
23+
24+
// keys returns the keys of a string map. It is used to print out supported group names.
25+
func keys(m map[string]string) []string {
26+
out := make([]string, 0, len(m))
27+
for k := range m {
28+
out = append(out, k)
29+
}
30+
return out
31+
}
32+
33+
// parseFullyQualifiedCRD takes a fully qualified CRD type of the form "resource.group"
34+
// (for example, "cephclusters.ceph.rook.io") and returns the resource name, group name, and
35+
// the API version associated with that group. It returns an error if the format is invalid
36+
// or the group is not recognized.
37+
func parseFullyQualifiedCRD(fqcrd string) (resourceName, groupName, version string, err error) {
38+
parts := strings.SplitN(fqcrd, ".", 2)
39+
if len(parts) != 2 {
40+
return "", "", "", fmt.Errorf("invalid CRD format %q; expected format <resource>.<group>", fqcrd)
41+
}
42+
resourceName = parts[0]
43+
groupName = parts[1]
44+
45+
ver, ok := groupVersions[groupName]
46+
if !ok {
47+
return "", "", "", fmt.Errorf("unsupported group %q; supported groups are: %v", groupName, keys(groupVersions))
48+
}
49+
return resourceName, groupName, ver, nil
50+
}
51+
52+
func contains(slice []string, item string) bool {
53+
for _, s := range slice {
54+
if s == item {
55+
return true
56+
}
57+
}
58+
return false
59+
}
60+
1061
// deletedCmd represents the deleted command
1162
var deletedCmd = &cobra.Command{
1263
Use: "deleted",
@@ -20,8 +71,54 @@ var deletedCmd = &cobra.Command{
2071
},
2172
Run: func(cmd *cobra.Command, args []string) {
2273
k8sutil.SetDeploymentScale(cmd.Context(), root.ClientSets.Kube, root.OperatorNamespace, "ocs-operator", 0)
23-
pkgrestore.RestoreCrd(cmd.Context(), root.ClientSets, root.OperatorNamespace, root.StorageClusterNamespace, args)
24-
k8sutil.SetDeploymentScale(cmd.Context(), root.ClientSets.Kube, root.OperatorNamespace, "ocs-operator", 1)
74+
// Parse the fully qualified CRD (e.g. "cephclusters.ceph.rook.io").
75+
resourceName, groupName, version, err := parseFullyQualifiedCRD(args[0])
76+
if err != nil {
77+
logging.Fatal(fmt.Errorf("Error parsing CRD type: %v\n", err))
78+
}
79+
// Construct a new args slice with the resource name as the first argument.
80+
newArgs := make([]string, len(args))
81+
newArgs[0] = resourceName
82+
if len(args) > 1 {
83+
newArgs[1] = args[1]
84+
}
85+
var customResources []pkgrestore.CustomResource
86+
if contains(newArgs, "storageclusters") {
87+
customResources = []pkgrestore.CustomResource{
88+
// ceph.rook.io/v1
89+
{Group: "ceph.rook.io", Version: "v1", Resource: "cephblockpoolradosnamespaces"},
90+
{Group: "ceph.rook.io", Version: "v1", Resource: "cephblockpools"},
91+
{Group: "ceph.rook.io", Version: "v1", Resource: "cephbucketnotifications"},
92+
{Group: "ceph.rook.io", Version: "v1", Resource: "cephbuckettopics"},
93+
{Group: "ceph.rook.io", Version: "v1", Resource: "cephclients"},
94+
{Group: "ceph.rook.io", Version: "v1", Resource: "cephclusters"},
95+
{Group: "ceph.rook.io", Version: "v1", Resource: "cephcosidrivers"},
96+
{Group: "ceph.rook.io", Version: "v1", Resource: "cephfilesystemmirrors"},
97+
{Group: "ceph.rook.io", Version: "v1", Resource: "cephfilesystems"},
98+
{Group: "ceph.rook.io", Version: "v1", Resource: "cephfilesystemsubvolumegroups"},
99+
{Group: "ceph.rook.io", Version: "v1", Resource: "cephnfses"},
100+
{Group: "ceph.rook.io", Version: "v1", Resource: "cephobjectrealms"},
101+
{Group: "ceph.rook.io", Version: "v1", Resource: "cephobjectstores"},
102+
{Group: "ceph.rook.io", Version: "v1", Resource: "cephobjectstoreusers"},
103+
{Group: "ceph.rook.io", Version: "v1", Resource: "cephobjectzonegroups"},
104+
{Group: "ceph.rook.io", Version: "v1", Resource: "cephobjectzones"},
105+
{Group: "ceph.rook.io", Version: "v1", Resource: "cephrbdmirrors"},
25106

107+
// noobaa.io/v1alpha1
108+
{Group: "noobaa.io", Version: "v1alpha1", Resource: "backingstores"},
109+
{Group: "noobaa.io", Version: "v1alpha1", Resource: "bucketclasses"},
110+
{Group: "noobaa.io", Version: "v1alpha1", Resource: "namespacestores"},
111+
{Group: "noobaa.io", Version: "v1alpha1", Resource: "noobaaaccounts"},
112+
{Group: "noobaa.io", Version: "v1alpha1", Resource: "noobaas"},
113+
114+
// ocs.openshift.io/v1alpha1
115+
{Group: "ocs.openshift.io", Version: "v1alpha1", Resource: "storageconsumers"},
116+
}
117+
} else {
118+
customResources = []pkgrestore.CustomResource{}
119+
}
120+
121+
pkgrestore.RestoreCrd(cmd.Context(), root.ClientSets, root.OperatorNamespace, root.StorageClusterNamespace, groupName, version, "ocs-operator", customResources, newArgs)
122+
k8sutil.SetDeploymentScale(cmd.Context(), root.ClientSets.Kube, root.OperatorNamespace, "ocs-operator", 1)
26123
},
27124
}

cmd/odf/restore/restore_test.go

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package restore
2+
3+
import (
4+
"github.com/stretchr/testify/assert"
5+
"testing"
6+
)
7+
8+
func Test_parseFullyQualifiedCRD(t *testing.T) {
9+
tests := []struct {
10+
name string
11+
input string
12+
expectResource string
13+
expectGroup string
14+
expectVersion string
15+
expectErr bool
16+
expectedErrMsg string
17+
}{
18+
{
19+
name: "valid CRD: cephclusters.ceph.rook.io",
20+
input: "cephclusters.ceph.rook.io",
21+
expectResource: "cephclusters",
22+
expectGroup: "ceph.rook.io",
23+
expectVersion: "v1",
24+
},
25+
{
26+
name: "valid CRD: storageclusters.ocs.openshift.io",
27+
input: "storageclusters.ocs.openshift.io",
28+
expectResource: "storageclusters",
29+
expectGroup: "ocs.openshift.io",
30+
expectVersion: "v1",
31+
},
32+
{
33+
name: "valid CRD: cephconnections.csi.ceph.io",
34+
input: "cephconnections.csi.ceph.io",
35+
expectResource: "cephconnections",
36+
expectGroup: "csi.ceph.io",
37+
expectVersion: "v1beta1",
38+
},
39+
{
40+
name: "valid CRD: storagesystems.odf.openshift.io",
41+
input: "storagesystems.odf.openshift.io",
42+
expectResource: "storagesystems",
43+
expectGroup: "odf.openshift.io",
44+
expectVersion: "v1alpha1",
45+
},
46+
{
47+
name: "invalid format: missing dot",
48+
input: "invalidformat",
49+
expectErr: true,
50+
expectedErrMsg: "invalid CRD format",
51+
},
52+
{
53+
name: "unsupported group",
54+
input: "foo.unsupported.group",
55+
expectErr: true,
56+
expectedErrMsg: "unsupported group",
57+
},
58+
}
59+
60+
for _, tt := range tests {
61+
tt := tt // capture range variable
62+
t.Run(tt.name, func(t *testing.T) {
63+
resource, group, version, err := parseFullyQualifiedCRD(tt.input)
64+
if tt.expectErr {
65+
assert.Error(t, err)
66+
assert.Contains(t, err.Error(), tt.expectedErrMsg)
67+
} else {
68+
assert.NoError(t, err)
69+
assert.Equal(t, tt.expectResource, resource)
70+
assert.Equal(t, tt.expectGroup, group)
71+
assert.Equal(t, tt.expectVersion, version)
72+
}
73+
})
74+
}
75+
}

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ require (
88
github.com/pkg/errors v0.9.1
99
github.com/ramendr/ramenctl v0.4.0
1010
github.com/red-hat-storage/ocs-operator/api/v4 v4.0.0-20240701091545-dfffbde82a9d
11-
github.com/rook/kubectl-rook-ceph v0.9.3
11+
github.com/rook/kubectl-rook-ceph v0.9.4-0.20250428051344-dbfe77cc57a1
1212
github.com/rook/rook v1.17.1
1313
github.com/spf13/cobra v1.9.1
1414
github.com/stretchr/testify v1.10.0

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -743,8 +743,8 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE
743743
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
744744
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
745745
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
746-
github.com/rook/kubectl-rook-ceph v0.9.3 h1:+7THA8a+S2ArJrs9jpY1eJscAjPBKjlLXSmCVPU3eoY=
747-
github.com/rook/kubectl-rook-ceph v0.9.3/go.mod h1:dOQ+Yccc41DxZqe9jpvAUHsYTquYP/SKClrPmG70SLM=
746+
github.com/rook/kubectl-rook-ceph v0.9.4-0.20250428051344-dbfe77cc57a1 h1:znvPe0apxkTdkdiVINk0DfUbMGt6Vv+I9mgKfsr3odY=
747+
github.com/rook/kubectl-rook-ceph v0.9.4-0.20250428051344-dbfe77cc57a1/go.mod h1:UvOPYgfA3+ulKE2omD+A6g82hst24jwO4gY9JIZqfu8=
748748
github.com/rook/rook v1.17.1 h1:nNUGesl5UtCVGi8Ta/Gl2m5z9rNmqnEzqSjY6vRFzKA=
749749
github.com/rook/rook v1.17.1/go.mod h1:Z5nuT7cGJLek9nRDH92J3XTcdqGMIyGDsFYo/O3NuT0=
750750
github.com/rook/rook/pkg/apis v0.0.0-20241216163035-3170ac6a0c58 h1:z7TkRb4D+XvXIGg4ClSjZw8gAPQcZg9zUSFfZ0pUcTY=

0 commit comments

Comments
 (0)