github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/cluster/connect_test.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package cluster 21 22 import ( 23 "net/http" 24 25 . "github.com/onsi/ginkgo/v2" 26 . "github.com/onsi/gomega" 27 28 corev1 "k8s.io/api/core/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/runtime/schema" 32 "k8s.io/cli-runtime/pkg/genericiooptions" 33 "k8s.io/cli-runtime/pkg/resource" 34 "k8s.io/client-go/kubernetes/scheme" 35 clientfake "k8s.io/client-go/rest/fake" 36 cmdtesting "k8s.io/kubectl/pkg/cmd/testing" 37 38 "github.com/1aal/kubeblocks/pkg/cli/exec" 39 "github.com/1aal/kubeblocks/pkg/cli/testing" 40 "github.com/1aal/kubeblocks/pkg/cli/types" 41 ) 42 43 var _ = Describe("connection", func() { 44 const ( 45 namespace = "test" 46 clusterName = "test" 47 ) 48 49 var ( 50 streams genericiooptions.IOStreams 51 tf *cmdtesting.TestFactory 52 ) 53 54 BeforeEach(func() { 55 tf = cmdtesting.NewTestFactory().WithNamespace("test") 56 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) 57 cluster := testing.FakeCluster(clusterName, namespace) 58 pods := testing.FakePods(3, namespace, clusterName) 59 httpResp := func(obj runtime.Object) *http.Response { 60 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, obj)} 61 } 62 tf.UnstructuredClient = &clientfake.RESTClient{ 63 GroupVersion: schema.GroupVersion{Group: types.AppsAPIGroup, Version: types.AppsAPIVersion}, 64 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer, 65 Client: clientfake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { 66 urlPrefix := "/api/v1/namespaces/" + namespace 67 return map[string]*http.Response{ 68 urlPrefix + "/services": httpResp(testing.FakeServices()), 69 urlPrefix + "/secrets": httpResp(testing.FakeSecrets(namespace, clusterName)), 70 urlPrefix + "/pods": httpResp(pods), 71 urlPrefix + "/pods/test-pod-0": httpResp(findPod(pods, "test-pod-0")), 72 }[req.URL.Path], nil 73 }), 74 } 75 76 tf.Client = tf.UnstructuredClient 77 tf.FakeDynamicClient = testing.FakeDynamicClient(cluster, testing.FakeClusterDef(), testing.FakeClusterVersion()) 78 streams = genericiooptions.NewTestIOStreamsDiscard() 79 }) 80 81 AfterEach(func() { 82 tf.Cleanup() 83 }) 84 85 It("new connection command", func() { 86 cmd := NewConnectCmd(tf, streams) 87 Expect(cmd).ShouldNot(BeNil()) 88 }) 89 90 It("validate", func() { 91 o := &ConnectOptions{ExecOptions: exec.NewExecOptions(tf, streams)} 92 93 By("specified more than one cluster") 94 Expect(o.validate([]string{"c1", "c2"})).Should(HaveOccurred()) 95 96 By("without cluster name") 97 Expect(o.validate(nil)).Should(HaveOccurred()) 98 99 Expect(o.validate([]string{clusterName})).Should(Succeed()) 100 101 // set instance name and cluster name, should fail 102 o.PodName = "test-pod-0" 103 Expect(o.validate([]string{clusterName})).Should(HaveOccurred()) 104 o.componentName = "test-component" 105 Expect(o.validate([]string{})).Should(HaveOccurred()) 106 107 // unset pod name 108 o.PodName = "" 109 Expect(o.validate([]string{clusterName})).Should(Succeed()) 110 // unset component name 111 o.componentName = "" 112 Expect(o.validate([]string{clusterName})).Should(Succeed()) 113 }) 114 115 It("complete by cluster name", func() { 116 o := &ConnectOptions{ExecOptions: exec.NewExecOptions(tf, streams)} 117 Expect(o.validate([]string{clusterName})).Should(Succeed()) 118 Expect(o.complete()).Should(Succeed()) 119 Expect(o.Pod).ShouldNot(BeNil()) 120 }) 121 122 It("complete by pod name", func() { 123 o := &ConnectOptions{ExecOptions: exec.NewExecOptions(tf, streams)} 124 o.PodName = "test-pod-0" 125 Expect(o.validate([]string{})).Should(Succeed()) 126 Expect(o.complete()).Should(Succeed()) 127 Expect(o.Pod).ShouldNot(BeNil()) 128 }) 129 130 It("show example", func() { 131 o := &ConnectOptions{ExecOptions: exec.NewExecOptions(tf, streams)} 132 Expect(o.validate([]string{clusterName})).Should(Succeed()) 133 Expect(o.complete()).Should(Succeed()) 134 135 By("specify one cluster") 136 Expect(o.runShowExample()).Should(Succeed()) 137 }) 138 139 Context("getConnectionInfo", func() { 140 const ( 141 user = "test-user" 142 password = "test-password" 143 ) 144 secret := corev1.Secret{} 145 secret.Name = "test-conn-credential" 146 secret.Data = map[string][]byte{ 147 "username": []byte(user), 148 "password": []byte(password), 149 } 150 secretList := &corev1.SecretList{} 151 secretList.Items = []corev1.Secret{secret} 152 It("getUserAndPassword", func() { 153 u, p, err := getUserAndPassword(testing.FakeClusterDef(), secretList) 154 Expect(err).Should(Succeed()) 155 Expect(u).Should(Equal(user)) 156 Expect(p).Should(Equal(password)) 157 }) 158 159 It("--show-password", func() { 160 o := &ConnectOptions{ExecOptions: exec.NewExecOptions(tf, streams)} 161 Expect(o.validate([]string{clusterName})).Should(Succeed()) 162 Expect(o.complete()).Should(Succeed()) 163 info, err := o.getConnectionInfo() 164 Expect(err).Should(Succeed()) 165 Expect(info.Password).Should(Equal(passwordMask)) 166 o.showPassword = true 167 info, err = o.getConnectionInfo() 168 Expect(err).Should(Succeed()) 169 Expect(info.Password).Should(Equal(password)) 170 }) 171 }) 172 }) 173 174 func mockPod() *corev1.Pod { 175 return &corev1.Pod{ 176 ObjectMeta: metav1.ObjectMeta{ 177 Name: "foo", 178 Namespace: "test", 179 ResourceVersion: "10", 180 Labels: map[string]string{ 181 "app.kubernetes.io/name": "mysql-apecloud-mysql", 182 }, 183 }, 184 Spec: corev1.PodSpec{ 185 RestartPolicy: corev1.RestartPolicyAlways, 186 DNSPolicy: corev1.DNSClusterFirst, 187 Containers: []corev1.Container{ 188 { 189 Name: "bar", 190 }, 191 }, 192 }, 193 Status: corev1.PodStatus{ 194 Phase: corev1.PodRunning, 195 }, 196 } 197 } 198 199 func findPod(pods *corev1.PodList, name string) *corev1.Pod { 200 for i, pod := range pods.Items { 201 if pod.Name == name { 202 return &pods.Items[i] 203 } 204 } 205 return nil 206 }