Skip to content

Commit 87316b8

Browse files
pkg/karmadactl/cmdinit: unit test clusterinfo
In this commit, we unit test clusterinfo on creating bootstrap configmap and creating cluster info RBAC rules. Signed-off-by: Mohamed Awnallah <[email protected]>
1 parent 9c0bd72 commit 87316b8

File tree

3 files changed

+211
-3
lines changed

3 files changed

+211
-3
lines changed

Diff for: pkg/karmadactl/cmdinit/bootstraptoken/clusterinfo/clusterinfo.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const (
3838
)
3939

4040
// CreateBootstrapConfigMapIfNotExists creates the kube-public ConfigMap if it doesn't exist already
41-
func CreateBootstrapConfigMapIfNotExists(clientSet *kubernetes.Clientset, file string) error {
41+
func CreateBootstrapConfigMapIfNotExists(clientSet kubernetes.Interface, file string) error {
4242
klog.V(1).Infoln("[bootstrap-token] loading karmada admin kubeconfig")
4343
adminConfig, err := clientcmd.LoadFromFile(file)
4444
if err != nil {
@@ -75,7 +75,7 @@ func CreateBootstrapConfigMapIfNotExists(clientSet *kubernetes.Clientset, file s
7575
}
7676

7777
// CreateClusterInfoRBACRules creates the RBAC rules for exposing the cluster-info ConfigMap in the kube-public namespace to unauthenticated users
78-
func CreateClusterInfoRBACRules(clientSet *kubernetes.Clientset) error {
78+
func CreateClusterInfoRBACRules(clientSet kubernetes.Interface) error {
7979
klog.V(1).Info("Creating the RBAC rules for exposing the cluster-info ConfigMap in the kube-public namespace")
8080
err := cmdutil.CreateOrUpdateRole(clientSet, &rbacv1.Role{
8181
ObjectMeta: metav1.ObjectMeta{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
/*
2+
Copyright 2024 The Karmada Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package clusterinfo
18+
19+
import (
20+
"context"
21+
"encoding/base64"
22+
"fmt"
23+
"os"
24+
"path/filepath"
25+
"reflect"
26+
"strings"
27+
"testing"
28+
29+
rbacv1 "k8s.io/api/rbac/v1"
30+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31+
"k8s.io/apiserver/pkg/authentication/user"
32+
"k8s.io/client-go/kubernetes"
33+
clientset "k8s.io/client-go/kubernetes"
34+
fakeclientset "k8s.io/client-go/kubernetes/fake"
35+
bootstrapapi "k8s.io/cluster-bootstrap/token/api"
36+
37+
"github.com/karmada-io/karmada/operator/pkg/certs"
38+
)
39+
40+
func TestCreateBootstrapConfigMapIfNotExists(t *testing.T) {
41+
kubeAdminConfig := `
42+
apiVersion: v1
43+
clusters:
44+
- cluster:
45+
certificate-authority-data: %s
46+
server: https://test-cluster:6443
47+
name: test-cluster
48+
contexts:
49+
- context:
50+
cluster: test-cluster
51+
user: test-user
52+
name: test-context
53+
current-context: test-context
54+
kind: Config
55+
preferences: {}
56+
users:
57+
- name: test-user
58+
user:
59+
client-certificate-data: %s
60+
client-key-data: %s
61+
`
62+
tests := []struct {
63+
name string
64+
client clientset.Interface
65+
cfgFile string
66+
prep func(cfgFile string) error
67+
verify func(clientset.Interface) error
68+
cleanup func(cfgFile string) error
69+
wantErr bool
70+
errMsg string
71+
}{
72+
{
73+
name: "CreateBootstrapConfigMapIfNotExists_NonExistentConfigFile_FailedToLoadAdminKubeConfig",
74+
prep: func(string) error { return nil },
75+
verify: func(clientset.Interface) error { return nil },
76+
cleanup: func(string) error { return nil },
77+
wantErr: true,
78+
errMsg: "failed to load admin kubeconfig",
79+
},
80+
{
81+
name: "CreateBootstrapConfigMapIfNotExists_WithConfigFile_ConfigMapCreatedInKubePublicNamespace",
82+
client: fakeclientset.NewSimpleClientset(),
83+
cfgFile: filepath.Join(os.TempDir(), "config-temp.txt"),
84+
prep: func(cfgFile string) error {
85+
caKarmadaCert, err := certs.NewCertificateAuthority(certs.KarmadaCertAdmin())
86+
if err != nil {
87+
t.Fatalf("NewCertificateAuthority() returned an error: %v", err)
88+
}
89+
90+
kubeAdminConfig = fmt.Sprintf(
91+
kubeAdminConfig,
92+
base64.StdEncoding.EncodeToString(caKarmadaCert.CertData()),
93+
base64.StdEncoding.EncodeToString(caKarmadaCert.CertData()),
94+
base64.StdEncoding.EncodeToString(caKarmadaCert.KeyData()),
95+
)
96+
97+
err = os.WriteFile(cfgFile, []byte(kubeAdminConfig), 0600)
98+
if err != nil {
99+
return fmt.Errorf("failed to write kubeAdminConfig to file, got error: %v", err)
100+
}
101+
102+
return nil
103+
},
104+
cleanup: func(cfgFile string) error {
105+
if err := os.Remove(cfgFile); err != nil {
106+
return fmt.Errorf("failed to remove config file %s, got an error: %v", cfgFile, err)
107+
}
108+
return nil
109+
},
110+
verify: func(c clientset.Interface) error {
111+
return verifyKubeAdminKubeConfig(c)
112+
},
113+
wantErr: false,
114+
},
115+
}
116+
for _, test := range tests {
117+
t.Run(test.name, func(t *testing.T) {
118+
if err := test.prep(test.cfgFile); err != nil {
119+
t.Fatalf("failed prep before creating bootstrap config map, got error: %v", err)
120+
}
121+
defer func() {
122+
if err := test.cleanup(test.cfgFile); err != nil {
123+
t.Errorf("deferred cleanup failed: %v", err)
124+
}
125+
}()
126+
127+
err := CreateBootstrapConfigMapIfNotExists(test.client, test.cfgFile)
128+
if err == nil && test.wantErr {
129+
t.Fatal("expected and error, but got none")
130+
}
131+
if err != nil && !test.wantErr {
132+
t.Errorf("unexpected error, got: %v", err)
133+
}
134+
if err != nil && test.wantErr && !strings.Contains(err.Error(), test.errMsg) {
135+
t.Errorf("expected error message %s to be in %s", test.errMsg, err.Error())
136+
}
137+
if err := test.verify(test.client); err != nil {
138+
t.Errorf("failed to verify creating bootstrap config map, got an error: %v", err)
139+
}
140+
})
141+
}
142+
}
143+
144+
func TestCreateClusterInfoRBACRules(t *testing.T) {
145+
tests := []struct {
146+
name string
147+
client kubernetes.Interface
148+
verify func(clientset.Interface) error
149+
}{
150+
{
151+
name: "CreateClusterInfoRBACRules_CreateRolesAndRoleBindings_Created",
152+
client: fakeclientset.NewSimpleClientset(),
153+
verify: func(c clientset.Interface) error {
154+
// Verify that roles are created as expected.
155+
role, err := c.RbacV1().Roles(metav1.NamespacePublic).Get(context.TODO(), BootstrapSignerClusterRoleName, metav1.GetOptions{})
156+
if err != nil {
157+
return fmt.Errorf("failed to get role %s, got an error: %v", BootstrapSignerClusterRoleName, err)
158+
}
159+
expectedPolicyRoles := []rbacv1.PolicyRule{
160+
{
161+
Verbs: []string{"get"},
162+
APIGroups: []string{""},
163+
Resources: []string{"configmaps"},
164+
ResourceNames: []string{bootstrapapi.ConfigMapClusterInfo},
165+
},
166+
}
167+
if !reflect.DeepEqual(role.Rules, expectedPolicyRoles) {
168+
return fmt.Errorf("expected policy roles to be equal, expected %v but got %v", expectedPolicyRoles, role.Rules)
169+
}
170+
171+
// Verify that role bindings are created as expected.
172+
roleBinding, err := c.RbacV1().RoleBindings(metav1.NamespacePublic).Get(context.TODO(), BootstrapSignerClusterRoleName, metav1.GetOptions{})
173+
if err != nil {
174+
return fmt.Errorf("failed to get role binding %s, got an error: %v", BootstrapSignerClusterRoleName, err)
175+
}
176+
if roleBinding.RoleRef.Name != BootstrapSignerClusterRoleName {
177+
return fmt.Errorf("expected rolebinding ref name to be %s, but got %s", BootstrapSignerClusterRoleName, roleBinding.RoleRef.Name)
178+
}
179+
if roleBinding.Subjects[0].Kind == rbacv1.UserKind && roleBinding.Subjects[0].Name != user.Anonymous {
180+
return fmt.Errorf("expected role binding subject user to be %s, but got %s", user.Anonymous, roleBinding.Subjects[0].Name)
181+
}
182+
183+
return nil
184+
},
185+
},
186+
}
187+
for _, test := range tests {
188+
t.Run(test.name, func(t *testing.T) {
189+
if err := CreateClusterInfoRBACRules(test.client); err != nil {
190+
t.Fatalf("failed to create cluster info RBAC rules, got error: %v", err)
191+
}
192+
if err := test.verify(test.client); err != nil {
193+
t.Errorf("failed to verify creating cluster info RBAC rules, got error: %v", err)
194+
}
195+
})
196+
}
197+
}
198+
199+
func verifyKubeAdminKubeConfig(client clientset.Interface) error {
200+
configMap, err := client.CoreV1().ConfigMaps(metav1.NamespacePublic).Get(context.TODO(), string(bootstrapapi.ConfigMapClusterInfo), metav1.GetOptions{})
201+
if err != nil {
202+
return fmt.Errorf("failed to get configmap %s, got an error: %v", string(bootstrapapi.ConfigMapClusterInfo), err)
203+
}
204+
if _, ok := configMap.Data[bootstrapapi.KubeConfigKey]; !ok {
205+
return fmt.Errorf("expected key %s to exist on the data field", bootstrapapi.KubeConfigKey)
206+
}
207+
return nil
208+
}

Diff for: pkg/karmadactl/util/idempotency.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ func CreateOrUpdateClusterRoleBinding(client kubernetes.Interface, clusterRoleBi
216216

217217
// CreateOrUpdateConfigMap creates a ConfigMap if the target resource doesn't exist.
218218
// If the resource exists already, this function will update the resource instead.
219-
func CreateOrUpdateConfigMap(client *kubernetes.Clientset, cm *corev1.ConfigMap) error {
219+
func CreateOrUpdateConfigMap(client kubernetes.Interface, cm *corev1.ConfigMap) error {
220220
if _, err := client.CoreV1().ConfigMaps(cm.Namespace).Create(context.TODO(), cm, metav1.CreateOptions{}); err != nil {
221221
if !apierrors.IsAlreadyExists(err) {
222222
return fmt.Errorf("unable to create ConfigMap: %v", err)

0 commit comments

Comments
 (0)