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 }