github.com/oam-dev/kubevela@v1.9.11/references/cli/velaql_test.go (about) 1 /* 2 Copyright 2021 The KubeVela 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 cli 18 19 import ( 20 "bytes" 21 "context" 22 "fmt" 23 "os" 24 "strconv" 25 "strings" 26 "time" 27 28 . "github.com/onsi/ginkgo/v2" 29 . "github.com/onsi/gomega" 30 "github.com/spf13/cobra" 31 corev1 "k8s.io/api/core/v1" 32 networkv1 "k8s.io/api/networking/v1" 33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 pkgtypes "k8s.io/apimachinery/pkg/types" 35 "k8s.io/apimachinery/pkg/util/intstr" 36 "k8s.io/utils/strings/slices" 37 "sigs.k8s.io/controller-runtime/pkg/client" 38 "sigs.k8s.io/yaml" 39 40 "github.com/oam-dev/kubevela/apis/core.oam.dev/common" 41 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 42 "github.com/oam-dev/kubevela/apis/types" 43 helmapi "github.com/oam-dev/kubevela/pkg/appfile/helm/flux2apis" 44 "github.com/oam-dev/kubevela/pkg/oam" 45 common2 "github.com/oam-dev/kubevela/pkg/utils/common" 46 ) 47 48 var _ = Describe("Test velaQL from file", func() { 49 It("Test Query pod data", func() { 50 cm := &corev1.ConfigMap{Data: map[string]string{"key": "my-value"}} 51 cm.Name = "mycm" 52 cm.Namespace = "default" 53 Expect(k8sClient.Create(context.TODO(), cm)).Should(BeNil()) 54 view := `import ( 55 "vela/ql" 56 ) 57 configmap: ql.#Read & { 58 value: { 59 kind: "ConfigMap" 60 apiVersion: "v1" 61 metadata: { 62 name: "mycm" 63 } 64 } 65 } 66 status: configmap.value.data.key 67 68 export: "status" 69 ` 70 name := "vela-test-" + strconv.FormatInt(time.Now().UnixNano(), 10) + ".cue" 71 Expect(os.WriteFile(name, []byte(view), 0644)).Should(BeNil()) 72 defer os.Remove(name) 73 74 arg := common2.Args{} 75 arg.SetConfig(cfg) 76 arg.SetClient(k8sClient) 77 cmd := NewCommand() 78 var buff = bytes.NewBufferString("") 79 cmd.SetOut(buff) 80 Expect(queryFromView(context.TODO(), arg, name, cmd)).Should(BeNil()) 81 Expect(strings.TrimSpace(buff.String())).Should(BeEquivalentTo("my-value")) 82 }) 83 }) 84 85 var _ = Describe("Test velaQL", func() { 86 var appName = "test-velaql" 87 var namespace = "default" 88 It("Test GetServiceEndpoints", func() { 89 arg := common2.Args{} 90 arg.SetConfig(cfg) 91 arg.SetClient(k8sClient) 92 93 // prepare 94 testApp := &v1beta1.Application{ 95 ObjectMeta: metav1.ObjectMeta{ 96 Name: appName, 97 Namespace: namespace, 98 }, 99 Spec: v1beta1.ApplicationSpec{ 100 Components: []common.ApplicationComponent{ 101 { 102 Name: "endpoints-test", 103 Type: "webservice", 104 }, 105 }, 106 }, 107 } 108 109 err := k8sClient.Create(context.TODO(), testApp) 110 Expect(err).Should(BeNil()) 111 112 testApp.Status = common.AppStatus{ 113 AppliedResources: []common.ClusterObjectReference{ 114 { 115 Cluster: "", 116 ObjectReference: corev1.ObjectReference{ 117 Kind: "Ingress", 118 Namespace: "default", 119 Name: "ingress-http", 120 APIVersion: "networking.k8s.io/v1beta1", 121 }, 122 }, 123 { 124 Cluster: "", 125 ObjectReference: corev1.ObjectReference{ 126 Kind: "Ingress", 127 Namespace: "default", 128 Name: "ingress-https", 129 APIVersion: "networking.k8s.io/v1", 130 }, 131 }, 132 { 133 Cluster: "", 134 ObjectReference: corev1.ObjectReference{ 135 Kind: "Ingress", 136 Namespace: "default", 137 Name: "ingress-paths", 138 APIVersion: "networking.k8s.io/v1", 139 }, 140 }, 141 { 142 Cluster: "", 143 ObjectReference: corev1.ObjectReference{ 144 Kind: "Service", 145 Namespace: "default", 146 Name: "nodeport", 147 APIVersion: "v1", 148 }, 149 }, 150 { 151 Cluster: "", 152 ObjectReference: corev1.ObjectReference{ 153 Kind: "Service", 154 Namespace: "default", 155 Name: "loadbalancer", 156 APIVersion: "v1", 157 }, 158 }, 159 { 160 Cluster: "", 161 ObjectReference: corev1.ObjectReference{ 162 Kind: helmapi.HelmReleaseGVK.Kind, 163 Namespace: "default", 164 Name: "helmRelease", 165 }, 166 }, 167 }, 168 } 169 170 err = k8sClient.Status().Update(context.TODO(), testApp) 171 Expect(err).Should(BeNil()) 172 173 var mr []v1beta1.ManagedResource 174 for i := range testApp.Status.AppliedResources { 175 mr = append(mr, v1beta1.ManagedResource{ 176 OAMObjectReference: common.OAMObjectReference{ 177 Component: "endpoints-test", 178 }, 179 ClusterObjectReference: testApp.Status.AppliedResources[i], 180 }) 181 } 182 rt := &v1beta1.ResourceTracker{ 183 ObjectMeta: metav1.ObjectMeta{ 184 Name: appName, 185 Namespace: namespace, 186 Labels: map[string]string{ 187 oam.LabelAppName: testApp.Name, 188 oam.LabelAppNamespace: testApp.Namespace, 189 }, 190 }, 191 Spec: v1beta1.ResourceTrackerSpec{ 192 Type: v1beta1.ResourceTrackerTypeRoot, 193 ManagedResources: mr, 194 }, 195 } 196 err = k8sClient.Create(context.TODO(), rt) 197 Expect(err).Should(BeNil()) 198 199 testServicelist := []map[string]interface{}{ 200 { 201 "name": "clusterip", 202 "ports": []corev1.ServicePort{ 203 {Port: 80, TargetPort: intstr.FromInt(80), Name: "80port"}, 204 {Port: 81, TargetPort: intstr.FromInt(81), Name: "81port"}, 205 }, 206 "type": corev1.ServiceTypeClusterIP, 207 }, 208 { 209 "name": "nodeport", 210 "ports": []corev1.ServicePort{ 211 {Port: 80, TargetPort: intstr.FromInt(80), NodePort: 30229}, 212 }, 213 "type": corev1.ServiceTypeNodePort, 214 }, 215 { 216 "name": "loadbalancer", 217 "ports": []corev1.ServicePort{ 218 {Port: 80, TargetPort: intstr.FromInt(80), Name: "80port", NodePort: 30180}, 219 {Port: 81, TargetPort: intstr.FromInt(81), Name: "81port", NodePort: 30181}, 220 }, 221 "type": corev1.ServiceTypeLoadBalancer, 222 "status": corev1.ServiceStatus{ 223 LoadBalancer: corev1.LoadBalancerStatus{ 224 Ingress: []corev1.LoadBalancerIngress{ 225 { 226 IP: "10.10.10.10", 227 }, 228 { 229 Hostname: "text.example.com", 230 }, 231 }, 232 }, 233 }, 234 }, 235 { 236 "name": "helm1", 237 "ports": []corev1.ServicePort{ 238 {Port: 80, NodePort: 30002, TargetPort: intstr.FromInt(80)}, 239 }, 240 "type": corev1.ServiceTypeNodePort, 241 "labels": map[string]string{ 242 "helm.toolkit.fluxcd.io/name": "helmRelease", 243 "helm.toolkit.fluxcd.io/namespace": "default", 244 }, 245 }, 246 } 247 for _, s := range testServicelist { 248 service := &corev1.Service{ 249 ObjectMeta: metav1.ObjectMeta{ 250 Name: s["name"].(string), 251 Namespace: "default", 252 }, 253 Spec: corev1.ServiceSpec{ 254 Ports: s["ports"].([]corev1.ServicePort), 255 Type: s["type"].(corev1.ServiceType), 256 }, 257 } 258 259 if s["labels"] != nil { 260 service.Labels = s["labels"].(map[string]string) 261 } 262 err := k8sClient.Create(context.TODO(), service) 263 Expect(err).Should(BeNil()) 264 if s["status"] != nil { 265 service.Status = s["status"].(corev1.ServiceStatus) 266 err := k8sClient.Status().Update(context.TODO(), service) 267 Expect(err).Should(BeNil()) 268 } 269 } 270 var prefixbeta = networkv1.PathTypePrefix 271 testIngress := []client.Object{ 272 &networkv1.Ingress{ 273 ObjectMeta: metav1.ObjectMeta{ 274 Name: "ingress-http", 275 Namespace: "default", 276 }, 277 Spec: networkv1.IngressSpec{ 278 Rules: []networkv1.IngressRule{ 279 { 280 Host: "ingress.domain", 281 IngressRuleValue: networkv1.IngressRuleValue{ 282 HTTP: &networkv1.HTTPIngressRuleValue{ 283 Paths: []networkv1.HTTPIngressPath{ 284 { 285 Path: "/", 286 Backend: networkv1.IngressBackend{ 287 Service: &networkv1.IngressServiceBackend{ 288 Name: "clusterip", 289 Port: networkv1.ServiceBackendPort{ 290 Number: 80, 291 }, 292 }, 293 }, 294 PathType: &prefixbeta, 295 }, 296 }, 297 }, 298 }, 299 }, 300 }, 301 }, 302 }, 303 &networkv1.Ingress{ 304 ObjectMeta: metav1.ObjectMeta{ 305 Name: "ingress-https", 306 Namespace: "default", 307 }, 308 Spec: networkv1.IngressSpec{ 309 TLS: []networkv1.IngressTLS{ 310 { 311 SecretName: "https-secret", 312 }, 313 }, 314 Rules: []networkv1.IngressRule{ 315 { 316 Host: "ingress.domain.https", 317 IngressRuleValue: networkv1.IngressRuleValue{ 318 HTTP: &networkv1.HTTPIngressRuleValue{ 319 Paths: []networkv1.HTTPIngressPath{ 320 { 321 Path: "/", 322 Backend: networkv1.IngressBackend{ 323 Service: &networkv1.IngressServiceBackend{ 324 Name: "clusterip", 325 Port: networkv1.ServiceBackendPort{ 326 Number: 80, 327 }, 328 }, 329 }, 330 PathType: &prefixbeta, 331 }, 332 }, 333 }, 334 }, 335 }, 336 }, 337 }, 338 }, 339 &networkv1.Ingress{ 340 ObjectMeta: metav1.ObjectMeta{ 341 Name: "ingress-paths", 342 Namespace: "default", 343 }, 344 Spec: networkv1.IngressSpec{ 345 TLS: []networkv1.IngressTLS{ 346 { 347 SecretName: "https-secret", 348 }, 349 }, 350 Rules: []networkv1.IngressRule{ 351 { 352 Host: "ingress.domain.path", 353 IngressRuleValue: networkv1.IngressRuleValue{ 354 HTTP: &networkv1.HTTPIngressRuleValue{ 355 Paths: []networkv1.HTTPIngressPath{ 356 { 357 Path: "/test", 358 Backend: networkv1.IngressBackend{ 359 Service: &networkv1.IngressServiceBackend{ 360 Name: "clusterip", 361 Port: networkv1.ServiceBackendPort{ 362 Number: 80, 363 }, 364 }, 365 }, 366 PathType: &prefixbeta, 367 }, 368 { 369 Path: "/test2", 370 Backend: networkv1.IngressBackend{ 371 Service: &networkv1.IngressServiceBackend{ 372 Name: "clusterip", 373 Port: networkv1.ServiceBackendPort{ 374 Number: 80, 375 }, 376 }, 377 }, 378 PathType: &prefixbeta, 379 }, 380 }, 381 }, 382 }, 383 }, 384 }, 385 }, 386 }, 387 &networkv1.Ingress{ 388 TypeMeta: metav1.TypeMeta{ 389 APIVersion: "networking.k8s.io/v1beta1", 390 }, 391 ObjectMeta: metav1.ObjectMeta{ 392 Name: "ingress-helm", 393 Namespace: "default", 394 Labels: map[string]string{ 395 "helm.toolkit.fluxcd.io/name": "helmRelease", 396 "helm.toolkit.fluxcd.io/namespace": "default", 397 }, 398 }, 399 Spec: networkv1.IngressSpec{ 400 Rules: []networkv1.IngressRule{ 401 { 402 Host: "ingress.domain.helm", 403 IngressRuleValue: networkv1.IngressRuleValue{ 404 HTTP: &networkv1.HTTPIngressRuleValue{ 405 Paths: []networkv1.HTTPIngressPath{ 406 { 407 Path: "/", 408 Backend: networkv1.IngressBackend{ 409 Service: &networkv1.IngressServiceBackend{ 410 Name: "clusterip", 411 Port: networkv1.ServiceBackendPort{ 412 Number: 80, 413 }, 414 }, 415 }, 416 PathType: &prefixbeta, 417 }, 418 }, 419 }, 420 }, 421 }, 422 }, 423 }, 424 }, 425 } 426 427 for _, ing := range testIngress { 428 err := k8sClient.Create(context.TODO(), ing) 429 Expect(err).Should(BeNil()) 430 } 431 var node corev1.NodeList 432 err = k8sClient.List(context.TODO(), &node) 433 Expect(err).Should(BeNil()) 434 var gatewayIP string 435 if len(node.Items) > 0 { 436 for _, address := range node.Items[0].Status.Addresses { 437 if address.Type == corev1.NodeInternalIP { 438 gatewayIP = address.Address 439 break 440 } 441 } 442 } 443 velaQL, err := os.ReadFile("../../charts/vela-core/templates/velaql/endpoints.yaml") 444 Expect(err).Should(BeNil()) 445 velaQLYaml := strings.Replace(string(velaQL), "{{ include \"systemDefinitionNamespace\" . }}", types.DefaultKubeVelaNS, 1) 446 var cm corev1.ConfigMap 447 err = yaml.Unmarshal([]byte(velaQLYaml), &cm) 448 Expect(err).Should(BeNil()) 449 err = k8sClient.Create(context.Background(), &cm) 450 Expect(err).Should(BeNil()) 451 endpoints, err := GetServiceEndpoints(context.TODO(), appName, namespace, arg, Filter{}) 452 Expect(err).Should(BeNil()) 453 urls := []string{ 454 "http://ingress.domain", 455 "https://ingress.domain.https", 456 "https://ingress.domain.path/test", 457 "https://ingress.domain.path/test2", 458 fmt.Sprintf("http://%s:30229", gatewayIP), 459 "http://10.10.10.10", 460 "http://text.example.com", 461 "10.10.10.10:81", 462 "text.example.com:81", 463 // helmRelease 464 fmt.Sprintf("http://%s:30002", gatewayIP), 465 "http://ingress.domain.helm", 466 } 467 for _, endpoint := range endpoints { 468 Expect(slices.Contains(urls, endpoint.String())).Should(BeTrue()) 469 } 470 }) 471 }) 472 473 func getViewConfigMap(name string) (*corev1.ConfigMap, error) { 474 cm := &corev1.ConfigMap{ 475 TypeMeta: metav1.TypeMeta{ 476 APIVersion: "v1", 477 Kind: "ConfigMap", 478 }, 479 ObjectMeta: metav1.ObjectMeta{ 480 Name: name, 481 Namespace: types.DefaultKubeVelaNS, 482 }, 483 } 484 485 err := k8sClient.Get(context.TODO(), pkgtypes.NamespacedName{ 486 Namespace: cm.GetNamespace(), 487 Name: cm.GetName(), 488 }, cm) 489 490 if err != nil { 491 return nil, err 492 } 493 494 return cm, nil 495 } 496 497 var _ = Describe("test NewQLApplyCommand", func() { 498 var c common2.Args 499 var cmd *cobra.Command 500 501 BeforeEach(func() { 502 c.SetClient(k8sClient) 503 c.SetConfig(cfg) 504 cmd = NewQLApplyCommand(c) 505 }) 506 507 It("no parameter provided", func() { 508 cmd.SetArgs([]string{}) 509 err := cmd.Execute() 510 Expect(err).ToNot(Succeed()) 511 Expect(err.Error()).To(ContainSubstring("no cue")) 512 }) 513 514 Context("from stdin", func() { 515 It("no view name specified", func() { 516 cmd.SetArgs([]string{"-f", "-"}) 517 err := cmd.Execute() 518 Expect(err).ToNot(Succeed()) 519 Expect(err.Error()).To(ContainSubstring("no view name")) 520 }) 521 }) 522 523 Context("from file", func() { 524 It("no view name specified, inferred from filename", func() { 525 cueStr := "something: {}\nstatus: something" 526 filename := "test-view" + strconv.FormatInt(time.Now().UnixNano(), 10) + ".cue" 527 err := os.WriteFile(filename, []byte(cueStr), 0600) 528 Expect(err).Should(Succeed()) 529 defer os.RemoveAll(filename) 530 cmd.SetArgs([]string{"-f", filename}) 531 err = cmd.Execute() 532 Expect(err).To(Succeed()) 533 _, err = getViewConfigMap(strings.TrimSuffix(filename, ".cue")) 534 Expect(err).To(Succeed()) 535 }) 536 }) 537 538 Context("from URL", func() { 539 It("invalid name inferred", func() { 540 cmd.SetArgs([]string{"-f", "https://some.com"}) 541 err := cmd.Execute() 542 Expect(err).ToNot(Succeed()) 543 Expect(err.Error()).To(ContainSubstring("view name should only")) 544 }) 545 }) 546 })