Skip to content

Commit 5bd1aca

Browse files
committed
feat- etcd migrate
Signed-off-by: Hélia Barroso <helia_barroso@hotmail.com>
1 parent 07e44b9 commit 5bd1aca

File tree

3 files changed

+319
-0
lines changed

3 files changed

+319
-0
lines changed

cmd/migrate.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright 2024 The prometheus-operator Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package cmd
16+
17+
import (
18+
"fmt"
19+
20+
"github.com/prometheus-operator/poctl/internal/etcdmigrate"
21+
"github.com/prometheus-operator/poctl/internal/k8sutil"
22+
"github.com/prometheus-operator/poctl/internal/log"
23+
"github.com/spf13/cobra"
24+
)
25+
26+
// migrateCmd represents the etcd store objects migration command.
27+
var migrateCmd = &cobra.Command{
28+
Use: "migrate",
29+
Short: "Automatically update Custom Resources to the latest storage version.",
30+
Long: `The migrate command in poctl automates the process of updating Kubernetes Custom Resources to the latest storage version. This is essential when upgrading a CRD that supports multiple API versions.`,
31+
RunE: runMigration,
32+
}
33+
34+
func init() {
35+
rootCmd.AddCommand(migrateCmd)
36+
}
37+
38+
func runMigration(cmd *cobra.Command, _ []string) error {
39+
logger, err := log.NewLogger()
40+
if err != nil {
41+
return fmt.Errorf("error while creating logger: %v", err)
42+
}
43+
clientSets, err := k8sutil.GetClientSets(kubeconfig)
44+
if err != nil {
45+
logger.Error("error while getting client sets", "err", err)
46+
return err
47+
}
48+
49+
if err := etcdmigrate.MigrateCRDs(cmd.Context(), clientSets); err != nil {
50+
logger.Error("error while updating etcd store", "err", err)
51+
}
52+
53+
logger.Info("Prometheus Operator CRD were update in etcd store ")
54+
return nil
55+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright 2024 The prometheus-operator Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package etcdmigrate
16+
17+
import (
18+
"context"
19+
"encoding/json"
20+
"fmt"
21+
22+
"github.com/prometheus-operator/poctl/internal/k8sutil"
23+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
25+
"k8s.io/apimachinery/pkg/runtime/schema"
26+
)
27+
28+
func MigrateCRDs(ctx context.Context, clientSets *k8sutil.ClientSets) error {
29+
crds, err := clientSets.APIExtensionsClient.ApiextensionsV1().CustomResourceDefinitions().List(ctx, metav1.ListOptions{})
30+
if err != nil {
31+
return fmt.Errorf("failed to list CRDs: %w", err)
32+
}
33+
34+
for _, crd := range crds.Items {
35+
if crd.Spec.Group != "monitoring.coreos.com" {
36+
continue
37+
}
38+
39+
var storageVersion string
40+
for _, version := range crd.Spec.Versions {
41+
if version.Storage {
42+
storageVersion = version.Name
43+
break
44+
}
45+
}
46+
if storageVersion == "" {
47+
continue
48+
}
49+
50+
crdResourceVersion := schema.GroupVersionResource{
51+
Group: crd.Spec.Group,
52+
Version: storageVersion,
53+
Resource: crd.Spec.Names.Plural,
54+
}
55+
56+
namespaces, err := clientSets.KClient.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
57+
if err != nil {
58+
return fmt.Errorf("failed to list Namespaces %v", err)
59+
}
60+
for _, namespace := range namespaces.Items {
61+
ns := namespace.Name
62+
63+
customResourcesInstances, err := clientSets.DClient.Resource(crdResourceVersion).Namespace(ns).List(ctx, metav1.ListOptions{})
64+
if err != nil {
65+
continue
66+
}
67+
68+
for _, cri := range customResourcesInstances.Items {
69+
name := cri.GetName()
70+
apiVersion := cri.GetAPIVersion()
71+
72+
expectedAPIVersion := fmt.Sprintf("%s/%s", crd.Spec.Group, storageVersion)
73+
if apiVersion == expectedAPIVersion {
74+
continue
75+
}
76+
77+
crdJSON, err := json.Marshal(cri.Object)
78+
if err != nil {
79+
continue
80+
}
81+
82+
var unstructuredeObject map[string]interface{}
83+
if err := json.Unmarshal(crdJSON, &unstructuredeObject); err != nil {
84+
continue
85+
}
86+
87+
unstructuredeObject["apiVersion"] = expectedAPIVersion
88+
89+
updatedStorageObject := &unstructured.Unstructured{Object: unstructuredeObject}
90+
91+
_, err = clientSets.DClient.Resource(crdResourceVersion).Namespace(ns).Create(ctx, updatedStorageObject, metav1.CreateOptions{})
92+
if err != nil {
93+
return fmt.Errorf("failed to create new version %s %s: %v", ns, name, err)
94+
}
95+
96+
err = clientSets.DClient.Resource(crdResourceVersion).Namespace(ns).Delete(ctx, name, metav1.DeleteOptions{})
97+
if err != nil {
98+
return fmt.Errorf("failed to delete old version of %s/%s: %v", ns, name, err)
99+
}
100+
}
101+
}
102+
}
103+
104+
fmt.Println("CRD migration completed.")
105+
return nil
106+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// Copyright 2024 The prometheus-operator Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package etcdmigrate
16+
17+
import (
18+
"context"
19+
"testing"
20+
21+
"github.com/prometheus-operator/poctl/internal/k8sutil"
22+
"github.com/stretchr/testify/assert"
23+
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
24+
fakeApiExtensions "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
25+
"k8s.io/apimachinery/pkg/api/errors"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
28+
"k8s.io/apimachinery/pkg/runtime"
29+
fakeDynamicClient "k8s.io/client-go/dynamic/fake"
30+
clienttesting "k8s.io/client-go/testing"
31+
)
32+
33+
func TestMigrateCRDs(t *testing.T) {
34+
type testCase struct {
35+
name string
36+
namespace string
37+
getMockedClientSets func(tc testCase) k8sutil.ClientSets
38+
shouldFail bool
39+
}
40+
41+
tests := []testCase{
42+
{
43+
name: "FailCRDList",
44+
shouldFail: true,
45+
getMockedClientSets: func(_ testCase) k8sutil.ClientSets {
46+
apiExtensionsClient := fakeApiExtensions.NewSimpleClientset()
47+
apiExtensionsClient.PrependReactor("list", "customresourcedefinitions", func(_ clienttesting.Action) (bool, runtime.Object, error) {
48+
return true, nil, errors.NewInternalError(nil)
49+
})
50+
51+
return k8sutil.ClientSets{
52+
APIExtensionsClient: apiExtensionsClient,
53+
}
54+
},
55+
},
56+
{
57+
name: "FailObjectUpdate",
58+
namespace: "test",
59+
shouldFail: true,
60+
getMockedClientSets: func(tc testCase) k8sutil.ClientSets {
61+
crd := &apiextensions.CustomResourceDefinition{
62+
ObjectMeta: metav1.ObjectMeta{Name: "testcrd"},
63+
Spec: apiextensions.CustomResourceDefinitionSpec{
64+
Group: "monitoring.coreos.com",
65+
Names: apiextensions.CustomResourceDefinitionNames{
66+
Plural: "probes",
67+
},
68+
Versions: []apiextensions.CustomResourceDefinitionVersion{},
69+
},
70+
}
71+
72+
crInstance := &unstructured.Unstructured{
73+
Object: map[string]interface{}{
74+
"apiVersion": "monitoring.coreos.com/v1beta1",
75+
"kind": "Probe",
76+
"metadata": map[string]interface{}{
77+
"name": "probe",
78+
"namespace": tc.namespace,
79+
},
80+
},
81+
}
82+
83+
apiExtensionsClient := fakeApiExtensions.NewSimpleClientset(crd)
84+
dClient := fakeDynamicClient.NewSimpleDynamicClient(runtime.NewScheme(), crInstance)
85+
dClient.PrependReactor("update", "probes", func(_ clienttesting.Action) (bool, runtime.Object, error) {
86+
return true, nil, errors.NewInternalError(nil)
87+
})
88+
89+
return k8sutil.ClientSets{
90+
APIExtensionsClient: apiExtensionsClient,
91+
DClient: dClient,
92+
}
93+
},
94+
},
95+
{
96+
name: "SuccessUpdateStorageVersion",
97+
namespace: "test",
98+
shouldFail: false,
99+
getMockedClientSets: func(tc testCase) k8sutil.ClientSets {
100+
crd := &apiextensions.CustomResourceDefinition{
101+
ObjectMeta: metav1.ObjectMeta{Name: "testcrd"},
102+
Spec: apiextensions.CustomResourceDefinitionSpec{
103+
Group: "monitoring.coreos.com",
104+
Names: apiextensions.CustomResourceDefinitionNames{
105+
Plural: "probes",
106+
},
107+
Versions: []apiextensions.CustomResourceDefinitionVersion{
108+
{Name: "v1", Storage: true},
109+
},
110+
},
111+
}
112+
113+
crInstance := &unstructured.Unstructured{
114+
Object: map[string]interface{}{
115+
"apiVersion": "monitoring.coreos.com/v1beta1",
116+
"kind": "Probe",
117+
"metadata": map[string]interface{}{
118+
"name": "probe",
119+
"namespace": tc.namespace,
120+
},
121+
},
122+
}
123+
124+
apiExtensionsClient := fakeApiExtensions.NewSimpleClientset(crd)
125+
dClient := fakeDynamicClient.NewSimpleDynamicClient(runtime.NewScheme(), crInstance)
126+
127+
dClient.PrependReactor("update", "probes", func(action clienttesting.Action) (bool, runtime.Object, error) {
128+
updateAction, _ := action.(clienttesting.UpdateAction)
129+
obj := updateAction.GetObject().(*unstructured.Unstructured)
130+
apiVersion := obj.GetAPIVersion()
131+
132+
if apiVersion != "monitoring.coreos.com/v1" {
133+
return true, nil, errors.NewInternalError(nil)
134+
}
135+
136+
return true, obj, nil
137+
})
138+
139+
return k8sutil.ClientSets{
140+
APIExtensionsClient: apiExtensionsClient,
141+
DClient: dClient,
142+
}
143+
},
144+
},
145+
}
146+
for _, tc := range tests {
147+
t.Run(tc.name, func(t *testing.T) {
148+
clientSets := tc.getMockedClientSets(tc)
149+
150+
err := MigrateCRDs(context.Background(), &clientSets)
151+
if tc.shouldFail {
152+
assert.Error(t, err)
153+
} else {
154+
assert.NoError(t, err)
155+
}
156+
})
157+
}
158+
}

0 commit comments

Comments
 (0)