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  })