github.com/oam-dev/kubevela@v1.9.11/pkg/velaql/providers/query/handler.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 query
    18  
    19  import (
    20  	"bufio"
    21  	"bytes"
    22  	"encoding/base64"
    23  	"fmt"
    24  	"io"
    25  	"time"
    26  
    27  	"github.com/pkg/errors"
    28  	corev1 "k8s.io/api/core/v1"
    29  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    31  	apimachinerytypes "k8s.io/apimachinery/pkg/types"
    32  	"k8s.io/client-go/kubernetes"
    33  	"k8s.io/client-go/rest"
    34  	"k8s.io/klog/v2"
    35  	"sigs.k8s.io/controller-runtime/pkg/client"
    36  
    37  	monitorContext "github.com/kubevela/pkg/monitor/context"
    38  	pkgmulticluster "github.com/kubevela/pkg/multicluster"
    39  	wfContext "github.com/kubevela/workflow/pkg/context"
    40  	"github.com/kubevela/workflow/pkg/cue/model/value"
    41  	"github.com/kubevela/workflow/pkg/types"
    42  
    43  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    44  	"github.com/oam-dev/kubevela/pkg/multicluster"
    45  	querytypes "github.com/oam-dev/kubevela/pkg/velaql/providers/query/types"
    46  )
    47  
    48  const (
    49  	// ProviderName is provider name for install.
    50  	ProviderName = "query"
    51  	// HelmReleaseKind is the kind of HelmRelease
    52  	HelmReleaseKind = "HelmRelease"
    53  
    54  	annoAmbassadorServiceName      = "ambassador.service/name"
    55  	annoAmbassadorServiceNamespace = "ambassador.service/namespace"
    56  )
    57  
    58  type provider struct {
    59  	cli client.Client
    60  	cfg *rest.Config
    61  }
    62  
    63  // Resource refer to an object with cluster info
    64  type Resource struct {
    65  	Cluster   string                     `json:"cluster"`
    66  	Component string                     `json:"component"`
    67  	Revision  string                     `json:"revision"`
    68  	Object    *unstructured.Unstructured `json:"object"`
    69  }
    70  
    71  // Option is the query option
    72  type Option struct {
    73  	Name      string       `json:"name"`
    74  	Namespace string       `json:"namespace"`
    75  	Filter    FilterOption `json:"filter,omitempty"`
    76  	// WithStatus means query the object from the cluster and get the latest status
    77  	// This field only suitable for ListResourcesInApp
    78  	WithStatus bool `json:"withStatus,omitempty"`
    79  
    80  	// WithTree means recursively query the resource tree.
    81  	WithTree bool `json:"withTree,omitempty"`
    82  }
    83  
    84  // FilterOption filter resource created by component
    85  type FilterOption struct {
    86  	Cluster          string   `json:"cluster,omitempty"`
    87  	ClusterNamespace string   `json:"clusterNamespace,omitempty"`
    88  	Components       []string `json:"components,omitempty"`
    89  	APIVersion       string   `json:"apiVersion,omitempty"`
    90  	Kind             string   `json:"kind,omitempty"`
    91  	QueryNewest      bool     `json:"queryNewest,omitempty"`
    92  }
    93  
    94  // ListResourcesInApp lists CRs created by Application, this provider queries the object data.
    95  func (h *provider) ListResourcesInApp(ctx monitorContext.Context, _ wfContext.Context, v *value.Value, _ types.Action) error {
    96  	val, err := v.LookupValue("app")
    97  	if err != nil {
    98  		return err
    99  	}
   100  	opt := Option{}
   101  	if err = val.UnmarshalTo(&opt); err != nil {
   102  		return err
   103  	}
   104  	collector := NewAppCollector(h.cli, opt)
   105  	appResList, err := collector.CollectResourceFromApp(ctx)
   106  	if err != nil {
   107  		return v.FillObject(err.Error(), "err")
   108  	}
   109  	if appResList == nil {
   110  		appResList = make([]Resource, 0)
   111  	}
   112  	return fillQueryResult(v, appResList, "list")
   113  }
   114  
   115  // ListAppliedResources list applied resource from tracker, this provider only queries the metadata.
   116  func (h *provider) ListAppliedResources(ctx monitorContext.Context, _ wfContext.Context, v *value.Value, _ types.Action) error {
   117  	val, err := v.LookupValue("app")
   118  	if err != nil {
   119  		return err
   120  	}
   121  	opt := Option{}
   122  	if err = val.UnmarshalTo(&opt); err != nil {
   123  		return v.FillObject(err.Error(), "err")
   124  	}
   125  	collector := NewAppCollector(h.cli, opt)
   126  	app := new(v1beta1.Application)
   127  	appKey := client.ObjectKey{Name: opt.Name, Namespace: opt.Namespace}
   128  	if err = h.cli.Get(ctx, appKey, app); err != nil {
   129  		return v.FillObject(err.Error(), "err")
   130  	}
   131  	appResList, err := collector.ListApplicationResources(ctx, app)
   132  	if err != nil {
   133  		return v.FillObject(err.Error(), "err")
   134  	}
   135  	if appResList == nil {
   136  		appResList = make([]*querytypes.AppliedResource, 0)
   137  	}
   138  	return fillQueryResult(v, appResList, "list")
   139  }
   140  
   141  func (h *provider) CollectResources(ctx monitorContext.Context, _ wfContext.Context, v *value.Value, _ types.Action) error {
   142  	val, err := v.LookupValue("app")
   143  	if err != nil {
   144  		return err
   145  	}
   146  	opt := Option{}
   147  	if err = val.UnmarshalTo(&opt); err != nil {
   148  		return v.FillObject(err.Error(), "err")
   149  	}
   150  	collector := NewAppCollector(h.cli, opt)
   151  	app := new(v1beta1.Application)
   152  	appKey := client.ObjectKey{Name: opt.Name, Namespace: opt.Namespace}
   153  	if err = h.cli.Get(ctx, appKey, app); err != nil {
   154  		return v.FillObject(err.Error(), "err")
   155  	}
   156  	appResList, err := collector.ListApplicationResources(ctx, app)
   157  	if err != nil {
   158  		return v.FillObject(err.Error(), "err")
   159  	}
   160  	var resources = make([]querytypes.ResourceItem, 0)
   161  	for _, res := range appResList {
   162  		if res.ResourceTree != nil {
   163  			resources = append(resources, buildResourceArray(*res, res.ResourceTree, res.ResourceTree, opt.Filter.Kind, opt.Filter.APIVersion)...)
   164  		} else if res.Kind == opt.Filter.Kind && res.APIVersion == opt.Filter.APIVersion {
   165  			var object unstructured.Unstructured
   166  			object.SetAPIVersion(opt.Filter.APIVersion)
   167  			object.SetKind(opt.Filter.Kind)
   168  			if err := h.cli.Get(ctx, apimachinerytypes.NamespacedName{Namespace: res.Namespace, Name: res.Name}, &object); err == nil {
   169  				resources = append(resources, buildResourceItem(*res, querytypes.Workload{
   170  					APIVersion: app.APIVersion,
   171  					Kind:       app.Kind,
   172  					Name:       app.Name,
   173  					Namespace:  app.Namespace,
   174  				}, object))
   175  			} else {
   176  				klog.Errorf("failed to get the service:%s", err.Error())
   177  			}
   178  		}
   179  	}
   180  	return fillQueryResult(v, resources, "list")
   181  }
   182  
   183  func (h *provider) SearchEvents(ctx monitorContext.Context, _ wfContext.Context, v *value.Value, _ types.Action) error {
   184  	val, err := v.LookupValue("value")
   185  	if err != nil {
   186  		return err
   187  	}
   188  	cluster, err := v.GetString("cluster")
   189  	if err != nil {
   190  		return err
   191  	}
   192  	obj := new(unstructured.Unstructured)
   193  	if err = val.UnmarshalTo(obj); err != nil {
   194  		return err
   195  	}
   196  
   197  	listCtx := multicluster.ContextWithClusterName(ctx, cluster)
   198  	fieldSelector := getEventFieldSelector(obj)
   199  	eventList := corev1.EventList{}
   200  	listOpts := []client.ListOption{
   201  		client.InNamespace(obj.GetNamespace()),
   202  		client.MatchingFieldsSelector{
   203  			Selector: fieldSelector,
   204  		},
   205  	}
   206  	if err := h.cli.List(listCtx, &eventList, listOpts...); err != nil {
   207  		return v.FillObject(err.Error(), "err")
   208  	}
   209  	return fillQueryResult(v, eventList.Items, "list")
   210  }
   211  
   212  func (h *provider) CollectLogsInPod(ctx monitorContext.Context, _ wfContext.Context, v *value.Value, _ types.Action) error {
   213  	cluster, err := v.GetString("cluster")
   214  	if err != nil {
   215  		return errors.Wrapf(err, "invalid cluster")
   216  	}
   217  	namespace, err := v.GetString("namespace")
   218  	if err != nil {
   219  		return errors.Wrapf(err, "invalid namespace")
   220  	}
   221  	pod, err := v.GetString("pod")
   222  	if err != nil {
   223  		return errors.Wrapf(err, "invalid pod name")
   224  	}
   225  	val, err := v.LookupValue("options")
   226  	if err != nil {
   227  		return errors.Wrapf(err, "invalid log options")
   228  	}
   229  	opts := &corev1.PodLogOptions{}
   230  	if err = val.UnmarshalTo(opts); err != nil {
   231  		return errors.Wrapf(err, "invalid log options content")
   232  	}
   233  	cliCtx := multicluster.ContextWithClusterName(ctx, cluster)
   234  	h.cfg.Wrap(pkgmulticluster.NewTransportWrapper())
   235  	clientSet, err := kubernetes.NewForConfig(h.cfg)
   236  	if err != nil {
   237  		return errors.Wrapf(err, "failed to create kubernetes client")
   238  	}
   239  	var defaultOutputs = make(map[string]interface{})
   240  	var errMsg string
   241  	podInst, err := clientSet.CoreV1().Pods(namespace).Get(cliCtx, pod, v1.GetOptions{})
   242  	if err != nil {
   243  		errMsg += fmt.Sprintf("failed to get pod: %s; ", err.Error())
   244  	}
   245  	req := clientSet.CoreV1().Pods(namespace).GetLogs(pod, opts)
   246  	readCloser, err := req.Stream(cliCtx)
   247  	if err != nil {
   248  		errMsg += fmt.Sprintf("failed to get stream logs %s; ", err.Error())
   249  	}
   250  	if readCloser != nil && podInst != nil {
   251  		r := bufio.NewReader(readCloser)
   252  		buffer := bytes.NewBuffer(nil)
   253  		var readErr error
   254  		defer func() {
   255  			_ = readCloser.Close()
   256  		}()
   257  		for {
   258  			s, err := r.ReadString('\n')
   259  			buffer.WriteString(s)
   260  			if err != nil {
   261  				if !errors.Is(err, io.EOF) {
   262  					readErr = err
   263  				}
   264  				break
   265  			}
   266  		}
   267  		toDate := v1.Now()
   268  		var fromDate v1.Time
   269  		// nolint
   270  		if opts.SinceTime != nil {
   271  			fromDate = *opts.SinceTime
   272  		} else if opts.SinceSeconds != nil {
   273  			fromDate = v1.NewTime(toDate.Add(time.Duration(-(*opts.SinceSeconds) * int64(time.Second))))
   274  		} else {
   275  			fromDate = podInst.CreationTimestamp
   276  		}
   277  		// the cue string can not support the special characters
   278  		logs := base64.StdEncoding.EncodeToString(buffer.Bytes())
   279  		defaultOutputs = map[string]interface{}{
   280  			"logs": logs,
   281  			"info": map[string]interface{}{
   282  				"fromDate": fromDate,
   283  				"toDate":   toDate,
   284  			},
   285  		}
   286  		if readErr != nil {
   287  			errMsg += readErr.Error()
   288  		}
   289  	}
   290  	if errMsg != "" {
   291  		klog.Warningf(errMsg)
   292  		defaultOutputs["err"] = errMsg
   293  	}
   294  	return v.FillObject(defaultOutputs, "outputs")
   295  }
   296  
   297  // Install register handlers to provider discover.
   298  func Install(p types.Providers, cli client.Client, cfg *rest.Config) {
   299  	prd := &provider{
   300  		cli: cli,
   301  		cfg: cfg,
   302  	}
   303  
   304  	p.Register(ProviderName, map[string]types.Handler{
   305  		"listResourcesInApp":      prd.ListResourcesInApp,
   306  		"listAppliedResources":    prd.ListAppliedResources,
   307  		"collectResources":        prd.CollectResources,
   308  		"searchEvents":            prd.SearchEvents,
   309  		"collectLogsInPod":        prd.CollectLogsInPod,
   310  		"collectServiceEndpoints": prd.CollectServiceEndpoints,
   311  	})
   312  }