2021-11-11 13:40:14 +08:00
|
|
|
/*
|
|
|
|
Copyright 2021. The KubeVela Authors.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package query
|
|
|
|
|
|
|
|
import (
|
2021-12-28 20:49:56 +08:00
|
|
|
"bufio"
|
2022-07-01 20:14:05 +08:00
|
|
|
"bytes"
|
2024-07-27 17:44:20 +08:00
|
|
|
"context"
|
|
|
|
_ "embed"
|
2022-07-01 20:14:05 +08:00
|
|
|
"encoding/base64"
|
2022-01-06 00:35:46 +08:00
|
|
|
"fmt"
|
2021-12-28 20:49:56 +08:00
|
|
|
"io"
|
2024-07-27 17:44:20 +08:00
|
|
|
"strings"
|
2021-12-28 20:49:56 +08:00
|
|
|
"time"
|
2021-11-11 13:40:14 +08:00
|
|
|
|
2021-12-28 20:49:56 +08:00
|
|
|
"github.com/pkg/errors"
|
2021-11-11 13:40:14 +08:00
|
|
|
corev1 "k8s.io/api/core/v1"
|
2021-12-28 20:49:56 +08:00
|
|
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
2021-11-11 13:40:14 +08:00
|
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
2022-08-01 19:44:27 +08:00
|
|
|
apimachinerytypes "k8s.io/apimachinery/pkg/types"
|
2021-12-28 20:49:56 +08:00
|
|
|
"k8s.io/client-go/kubernetes"
|
2022-12-02 14:50:06 +08:00
|
|
|
"k8s.io/klog/v2"
|
2021-11-11 13:40:14 +08:00
|
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
|
|
|
2024-07-27 17:44:20 +08:00
|
|
|
cuexruntime "github.com/kubevela/pkg/cue/cuex/runtime"
|
2022-12-06 15:49:39 +08:00
|
|
|
pkgmulticluster "github.com/kubevela/pkg/multicluster"
|
2024-07-27 17:44:20 +08:00
|
|
|
"github.com/kubevela/workflow/pkg/providers/legacy/kube"
|
2022-09-02 12:55:03 +08:00
|
|
|
|
2022-01-06 00:35:46 +08:00
|
|
|
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
2021-11-11 13:40:14 +08:00
|
|
|
"github.com/oam-dev/kubevela/pkg/multicluster"
|
2024-07-27 17:44:20 +08:00
|
|
|
querytypes "github.com/oam-dev/kubevela/pkg/utils/types"
|
2024-10-01 14:59:44 +08:00
|
|
|
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
2021-11-11 13:40:14 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// ProviderName is provider name for install.
|
|
|
|
ProviderName = "query"
|
2021-11-24 11:09:50 +08:00
|
|
|
// HelmReleaseKind is the kind of HelmRelease
|
|
|
|
HelmReleaseKind = "HelmRelease"
|
2022-02-20 13:06:24 +08:00
|
|
|
|
|
|
|
annoAmbassadorServiceName = "ambassador.service/name"
|
|
|
|
annoAmbassadorServiceNamespace = "ambassador.service/namespace"
|
2021-11-11 13:40:14 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
// Resource refer to an object with cluster info
|
|
|
|
type Resource struct {
|
2021-11-27 13:57:48 +08:00
|
|
|
Cluster string `json:"cluster"`
|
|
|
|
Component string `json:"component"`
|
|
|
|
Revision string `json:"revision"`
|
|
|
|
Object *unstructured.Unstructured `json:"object"`
|
2021-11-11 13:40:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Option is the query option
|
|
|
|
type Option struct {
|
2021-11-27 13:57:48 +08:00
|
|
|
Name string `json:"name"`
|
|
|
|
Namespace string `json:"namespace"`
|
|
|
|
Filter FilterOption `json:"filter,omitempty"`
|
2022-05-18 19:39:08 +08:00
|
|
|
// WithStatus means query the object from the cluster and get the latest status
|
|
|
|
// This field only suitable for ListResourcesInApp
|
|
|
|
WithStatus bool `json:"withStatus,omitempty"`
|
2022-08-01 19:44:27 +08:00
|
|
|
|
|
|
|
// WithTree means recursively query the resource tree.
|
|
|
|
WithTree bool `json:"withTree,omitempty"`
|
2021-11-21 14:56:01 +08:00
|
|
|
}
|
|
|
|
|
2021-11-27 13:57:48 +08:00
|
|
|
// FilterOption filter resource created by component
|
|
|
|
type FilterOption struct {
|
|
|
|
Cluster string `json:"cluster,omitempty"`
|
|
|
|
ClusterNamespace string `json:"clusterNamespace,omitempty"`
|
|
|
|
Components []string `json:"components,omitempty"`
|
2022-05-18 19:39:08 +08:00
|
|
|
APIVersion string `json:"apiVersion,omitempty"`
|
|
|
|
Kind string `json:"kind,omitempty"`
|
2023-06-09 09:44:36 +08:00
|
|
|
QueryNewest bool `json:"queryNewest,omitempty"`
|
2021-11-11 13:40:14 +08:00
|
|
|
}
|
|
|
|
|
2024-07-27 17:44:20 +08:00
|
|
|
// ListVars is the vars for list
|
|
|
|
type ListVars struct {
|
|
|
|
App Option `json:"app"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// ListParams is the params for list
|
|
|
|
type ListParams = oamprovidertypes.OAMParams[ListVars]
|
|
|
|
|
|
|
|
// ListResult is the result for list
|
|
|
|
type ListResult[T any] struct {
|
|
|
|
List []T `json:"list"`
|
|
|
|
Error string `json:"err,omitempty"`
|
|
|
|
}
|
|
|
|
|
2022-05-18 19:39:08 +08:00
|
|
|
// ListResourcesInApp lists CRs created by Application, this provider queries the object data.
|
2024-07-27 17:44:20 +08:00
|
|
|
func ListResourcesInApp(ctx context.Context, params *ListParams) (*ListResult[Resource], error) {
|
|
|
|
collector := NewAppCollector(params.KubeClient, params.Params.App)
|
2022-09-02 12:55:03 +08:00
|
|
|
appResList, err := collector.CollectResourceFromApp(ctx)
|
2021-11-11 13:40:14 +08:00
|
|
|
if err != nil {
|
2024-07-27 17:44:20 +08:00
|
|
|
// nolint:nilerr
|
|
|
|
return &ListResult[Resource]{Error: err.Error()}, nil
|
2021-11-11 13:40:14 +08:00
|
|
|
}
|
2022-05-30 15:48:28 +08:00
|
|
|
if appResList == nil {
|
|
|
|
appResList = make([]Resource, 0)
|
|
|
|
}
|
2024-07-27 17:44:20 +08:00
|
|
|
return &ListResult[Resource]{List: appResList}, nil
|
2021-11-11 13:40:14 +08:00
|
|
|
}
|
|
|
|
|
2022-05-18 19:39:08 +08:00
|
|
|
// ListAppliedResources list applied resource from tracker, this provider only queries the metadata.
|
2024-07-27 17:44:20 +08:00
|
|
|
func ListAppliedResources(ctx context.Context, params *ListParams) (*ListResult[querytypes.AppliedResource], error) {
|
|
|
|
opt := params.Params.App
|
|
|
|
cli := params.KubeClient
|
|
|
|
collector := NewAppCollector(cli, opt)
|
2022-03-11 21:22:58 +08:00
|
|
|
app := new(v1beta1.Application)
|
|
|
|
appKey := client.ObjectKey{Name: opt.Name, Namespace: opt.Namespace}
|
2024-07-27 17:44:20 +08:00
|
|
|
if err := cli.Get(ctx, appKey, app); err != nil {
|
|
|
|
// nolint:nilerr
|
|
|
|
return &ListResult[querytypes.AppliedResource]{Error: err.Error()}, nil
|
2022-03-11 21:22:58 +08:00
|
|
|
}
|
2022-09-19 10:23:10 +08:00
|
|
|
appResList, err := collector.ListApplicationResources(ctx, app)
|
2022-03-11 21:22:58 +08:00
|
|
|
if err != nil {
|
2024-07-27 17:44:20 +08:00
|
|
|
// nolint:nilerr
|
|
|
|
return &ListResult[querytypes.AppliedResource]{Error: err.Error()}, nil
|
2022-03-11 21:22:58 +08:00
|
|
|
}
|
2022-05-30 15:48:28 +08:00
|
|
|
if appResList == nil {
|
2024-07-27 17:44:20 +08:00
|
|
|
appResList = make([]querytypes.AppliedResource, 0)
|
2022-05-30 15:48:28 +08:00
|
|
|
}
|
2024-07-27 17:44:20 +08:00
|
|
|
return &ListResult[querytypes.AppliedResource]{List: appResList}, nil
|
2022-03-11 21:22:58 +08:00
|
|
|
}
|
|
|
|
|
2024-07-27 17:44:20 +08:00
|
|
|
// CollectResources collects resources from the cluster
|
|
|
|
func CollectResources(ctx context.Context, params *ListParams) (*ListResult[querytypes.ResourceItem], error) {
|
|
|
|
opt := params.Params.App
|
|
|
|
cli := params.KubeClient
|
|
|
|
collector := NewAppCollector(cli, opt)
|
2022-05-23 16:41:19 +08:00
|
|
|
app := new(v1beta1.Application)
|
|
|
|
appKey := client.ObjectKey{Name: opt.Name, Namespace: opt.Namespace}
|
2024-07-27 17:44:20 +08:00
|
|
|
if err := cli.Get(ctx, appKey, app); err != nil {
|
|
|
|
// nolint:nilerr
|
|
|
|
return &ListResult[querytypes.ResourceItem]{Error: err.Error()}, nil
|
2022-05-23 16:41:19 +08:00
|
|
|
}
|
2022-09-19 10:23:10 +08:00
|
|
|
appResList, err := collector.ListApplicationResources(ctx, app)
|
2022-05-23 16:41:19 +08:00
|
|
|
if err != nil {
|
2024-07-27 17:44:20 +08:00
|
|
|
// nolint:nilerr
|
|
|
|
return &ListResult[querytypes.ResourceItem]{Error: err.Error()}, nil
|
2022-05-23 16:41:19 +08:00
|
|
|
}
|
2022-08-01 19:44:27 +08:00
|
|
|
var resources = make([]querytypes.ResourceItem, 0)
|
|
|
|
for _, res := range appResList {
|
|
|
|
if res.ResourceTree != nil {
|
2024-07-27 17:44:20 +08:00
|
|
|
resources = append(resources, buildResourceArray(res, res.ResourceTree, res.ResourceTree, opt.Filter.Kind, opt.Filter.APIVersion)...)
|
2022-08-01 19:44:27 +08:00
|
|
|
} else if res.Kind == opt.Filter.Kind && res.APIVersion == opt.Filter.APIVersion {
|
2024-07-27 17:44:20 +08:00
|
|
|
object := &unstructured.Unstructured{}
|
2022-08-01 19:44:27 +08:00
|
|
|
object.SetAPIVersion(opt.Filter.APIVersion)
|
|
|
|
object.SetKind(opt.Filter.Kind)
|
2024-07-27 17:44:20 +08:00
|
|
|
if err := cli.Get(ctx, apimachinerytypes.NamespacedName{Namespace: res.Namespace, Name: res.Name}, object); err == nil {
|
|
|
|
resources = append(resources, buildResourceItem(res, querytypes.Workload{
|
2022-08-01 19:44:27 +08:00
|
|
|
APIVersion: app.APIVersion,
|
|
|
|
Kind: app.Kind,
|
|
|
|
Name: app.Name,
|
|
|
|
Namespace: app.Namespace,
|
|
|
|
}, object))
|
|
|
|
} else {
|
2022-12-02 14:50:06 +08:00
|
|
|
klog.Errorf("failed to get the service:%s", err.Error())
|
2022-08-01 19:44:27 +08:00
|
|
|
}
|
|
|
|
}
|
2021-11-11 13:40:14 +08:00
|
|
|
}
|
2024-07-27 17:44:20 +08:00
|
|
|
return &ListResult[querytypes.ResourceItem]{List: resources}, nil
|
2021-11-11 13:40:14 +08:00
|
|
|
}
|
|
|
|
|
2024-07-27 17:44:20 +08:00
|
|
|
// SearchVars is the vars for search
|
|
|
|
type SearchVars struct {
|
|
|
|
Value *unstructured.Unstructured `json:"value"`
|
|
|
|
Cluster string `json:"cluster"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// SearchParams is the params for search
|
|
|
|
type SearchParams = oamprovidertypes.OAMParams[SearchVars]
|
|
|
|
|
|
|
|
// SearchEvents searches events
|
|
|
|
func SearchEvents(ctx context.Context, params *SearchParams) (*ListResult[corev1.Event], error) {
|
|
|
|
obj := params.Params.Value
|
|
|
|
if obj == nil {
|
|
|
|
return nil, fmt.Errorf("please provide a object value to search events")
|
2021-11-11 13:40:14 +08:00
|
|
|
}
|
2024-07-27 17:44:20 +08:00
|
|
|
cluster := params.Params.Cluster
|
|
|
|
cli := params.KubeClient
|
2021-11-11 13:40:14 +08:00
|
|
|
|
2022-09-02 12:55:03 +08:00
|
|
|
listCtx := multicluster.ContextWithClusterName(ctx, cluster)
|
2021-11-11 13:40:14 +08:00
|
|
|
fieldSelector := getEventFieldSelector(obj)
|
|
|
|
eventList := corev1.EventList{}
|
|
|
|
listOpts := []client.ListOption{
|
|
|
|
client.InNamespace(obj.GetNamespace()),
|
|
|
|
client.MatchingFieldsSelector{
|
|
|
|
Selector: fieldSelector,
|
|
|
|
},
|
|
|
|
}
|
2024-07-27 17:44:20 +08:00
|
|
|
if err := cli.List(listCtx, &eventList, listOpts...); err != nil {
|
|
|
|
// nolint:nilerr
|
|
|
|
return &ListResult[corev1.Event]{Error: err.Error()}, nil
|
2021-11-11 13:40:14 +08:00
|
|
|
}
|
2024-07-27 17:44:20 +08:00
|
|
|
return &ListResult[corev1.Event]{List: eventList.Items}, nil
|
2021-11-11 13:40:14 +08:00
|
|
|
}
|
|
|
|
|
2024-07-27 17:44:20 +08:00
|
|
|
// LogVars is the vars for log
|
|
|
|
type LogVars struct {
|
|
|
|
Cluster string `json:"cluster"`
|
|
|
|
Namespace string `json:"namespace"`
|
|
|
|
Pod string `json:"pod"`
|
|
|
|
Options *corev1.PodLogOptions `json:"options,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// LogParams is the params for log
|
|
|
|
type LogParams = oamprovidertypes.OAMParams[LogVars]
|
|
|
|
|
|
|
|
// LogResult is the result for log
|
|
|
|
type LogResult struct {
|
|
|
|
Outputs map[string]interface{} `json:"outputs"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// CollectLogsInPod collects logs in pod
|
|
|
|
func CollectLogsInPod(ctx context.Context, params *LogParams) (*LogResult, error) {
|
|
|
|
cluster := params.Params.Cluster
|
|
|
|
namespace := params.Params.Namespace
|
|
|
|
pod := params.Params.Pod
|
|
|
|
if pod == "" {
|
|
|
|
return nil, fmt.Errorf("please provide a pod name to collect logs")
|
2021-12-28 20:49:56 +08:00
|
|
|
}
|
2024-07-27 17:44:20 +08:00
|
|
|
opts := params.Params.Options
|
|
|
|
if opts == nil || opts.Container == "" {
|
|
|
|
return nil, fmt.Errorf("please provide the container name to collect logs")
|
2021-12-28 20:49:56 +08:00
|
|
|
}
|
2023-03-07 16:19:37 +08:00
|
|
|
cliCtx := multicluster.ContextWithClusterName(ctx, cluster)
|
2024-07-27 17:44:20 +08:00
|
|
|
cfg := params.KubeConfig
|
|
|
|
cfg.Wrap(pkgmulticluster.NewTransportWrapper())
|
|
|
|
clientSet, err := kubernetes.NewForConfig(cfg)
|
2021-12-28 20:49:56 +08:00
|
|
|
if err != nil {
|
2024-07-27 17:44:20 +08:00
|
|
|
return nil, errors.Wrapf(err, "failed to create kubernetes client")
|
2021-12-28 20:49:56 +08:00
|
|
|
}
|
2022-07-01 20:14:05 +08:00
|
|
|
var defaultOutputs = make(map[string]interface{})
|
|
|
|
var errMsg string
|
2021-12-28 20:49:56 +08:00
|
|
|
podInst, err := clientSet.CoreV1().Pods(namespace).Get(cliCtx, pod, v1.GetOptions{})
|
|
|
|
if err != nil {
|
2023-01-05 15:42:38 +08:00
|
|
|
errMsg += fmt.Sprintf("failed to get pod: %s; ", err.Error())
|
2021-12-28 20:49:56 +08:00
|
|
|
}
|
|
|
|
req := clientSet.CoreV1().Pods(namespace).GetLogs(pod, opts)
|
|
|
|
readCloser, err := req.Stream(cliCtx)
|
2022-07-01 20:14:05 +08:00
|
|
|
if err != nil {
|
2023-01-05 15:42:38 +08:00
|
|
|
errMsg += fmt.Sprintf("failed to get stream logs %s; ", err.Error())
|
2021-12-28 20:49:56 +08:00
|
|
|
}
|
2022-07-01 20:14:05 +08:00
|
|
|
if readCloser != nil && podInst != nil {
|
|
|
|
r := bufio.NewReader(readCloser)
|
|
|
|
buffer := bytes.NewBuffer(nil)
|
|
|
|
var readErr error
|
2021-12-28 20:49:56 +08:00
|
|
|
defer func() {
|
|
|
|
_ = readCloser.Close()
|
|
|
|
}()
|
|
|
|
for {
|
|
|
|
s, err := r.ReadString('\n')
|
2022-07-01 20:14:05 +08:00
|
|
|
buffer.WriteString(s)
|
2021-12-28 20:49:56 +08:00
|
|
|
if err != nil {
|
|
|
|
if !errors.Is(err, io.EOF) {
|
|
|
|
readErr = err
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2022-07-01 20:14:05 +08:00
|
|
|
toDate := v1.Now()
|
|
|
|
var fromDate v1.Time
|
|
|
|
// nolint
|
|
|
|
if opts.SinceTime != nil {
|
|
|
|
fromDate = *opts.SinceTime
|
|
|
|
} else if opts.SinceSeconds != nil {
|
|
|
|
fromDate = v1.NewTime(toDate.Add(time.Duration(-(*opts.SinceSeconds) * int64(time.Second))))
|
|
|
|
} else {
|
|
|
|
fromDate = podInst.CreationTimestamp
|
|
|
|
}
|
|
|
|
// the cue string can not support the special characters
|
|
|
|
logs := base64.StdEncoding.EncodeToString(buffer.Bytes())
|
|
|
|
defaultOutputs = map[string]interface{}{
|
|
|
|
"logs": logs,
|
|
|
|
"info": map[string]interface{}{
|
|
|
|
"fromDate": fromDate,
|
|
|
|
"toDate": toDate,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
if readErr != nil {
|
2023-01-05 15:42:38 +08:00
|
|
|
errMsg += readErr.Error()
|
2022-07-01 20:14:05 +08:00
|
|
|
}
|
2021-12-28 20:49:56 +08:00
|
|
|
}
|
2022-07-01 20:14:05 +08:00
|
|
|
if errMsg != "" {
|
2025-04-29 03:46:08 +08:00
|
|
|
klog.Warningf("%s", errMsg)
|
2022-07-01 20:14:05 +08:00
|
|
|
defaultOutputs["err"] = errMsg
|
2021-12-28 20:49:56 +08:00
|
|
|
}
|
2024-07-27 17:44:20 +08:00
|
|
|
return &LogResult{Outputs: defaultOutputs}, nil
|
2021-12-28 20:49:56 +08:00
|
|
|
}
|
|
|
|
|
2024-07-27 17:44:20 +08:00
|
|
|
//go:embed ql.cue
|
|
|
|
var qlTemplate string
|
|
|
|
|
|
|
|
// GetTemplate returns the cue template.
|
|
|
|
func GetTemplate() string {
|
|
|
|
return strings.Join([]string{qlTemplate, kube.GetTemplate()}, "\n")
|
|
|
|
}
|
2021-11-11 13:40:14 +08:00
|
|
|
|
2024-07-27 17:44:20 +08:00
|
|
|
// GetProviders returns the cue providers.
|
|
|
|
func GetProviders() map[string]cuexruntime.ProviderFn {
|
|
|
|
qlProvider := map[string]cuexruntime.ProviderFn{
|
|
|
|
"listResourcesInApp": oamprovidertypes.OAMGenericProviderFn[ListVars, ListResult[Resource]](ListResourcesInApp),
|
|
|
|
"listAppliedResources": oamprovidertypes.OAMGenericProviderFn[ListVars, ListResult[querytypes.AppliedResource]](ListAppliedResources),
|
|
|
|
"collectResources": oamprovidertypes.OAMGenericProviderFn[ListVars, ListResult[querytypes.ResourceItem]](CollectResources),
|
|
|
|
"searchEvents": oamprovidertypes.OAMGenericProviderFn[SearchVars, ListResult[corev1.Event]](SearchEvents),
|
|
|
|
"collectLogsInPod": oamprovidertypes.OAMGenericProviderFn[LogVars, LogResult](CollectLogsInPod),
|
|
|
|
"collectServiceEndpoints": oamprovidertypes.OAMGenericProviderFn[ListVars, ListResult[querytypes.ServiceEndpoint]](CollectServiceEndpoints),
|
|
|
|
}
|
|
|
|
kubeProviders := kube.GetProviders()
|
|
|
|
for k, v := range kubeProviders {
|
|
|
|
qlProvider[k] = v
|
|
|
|
}
|
|
|
|
return qlProvider
|
2021-11-11 13:40:14 +08:00
|
|
|
}
|