github.com/oam-dev/kubevela@v1.9.11/pkg/utils/k8s.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 utils
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"os"
    24  	"regexp"
    25  	"strings"
    26  	"text/template"
    27  	"time"
    28  
    29  	"github.com/tidwall/gjson"
    30  	"k8s.io/apimachinery/pkg/fields"
    31  	"k8s.io/apimachinery/pkg/runtime"
    32  
    33  	"github.com/fatih/color"
    34  	"github.com/kubevela/pkg/multicluster"
    35  	"github.com/pkg/errors"
    36  	"github.com/wercker/stern/stern"
    37  	authv1 "k8s.io/api/authentication/v1"
    38  	corev1 "k8s.io/api/core/v1"
    39  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    40  	"k8s.io/apimachinery/pkg/api/meta"
    41  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    42  	"k8s.io/apimachinery/pkg/labels"
    43  	"k8s.io/apimachinery/pkg/runtime/schema"
    44  	"k8s.io/apimachinery/pkg/selection"
    45  	k8stypes "k8s.io/apimachinery/pkg/types"
    46  	"k8s.io/client-go/kubernetes"
    47  	"k8s.io/client-go/rest"
    48  	"sigs.k8s.io/controller-runtime/pkg/client"
    49  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    50  
    51  	"github.com/oam-dev/kubevela/pkg/oam/util"
    52  	velaerr "github.com/oam-dev/kubevela/pkg/utils/errors"
    53  	querytypes "github.com/oam-dev/kubevela/pkg/velaql/providers/query/types"
    54  )
    55  
    56  // MutateOption defines the function pattern for mutate
    57  type MutateOption func(object metav1.Object) error
    58  
    59  // MergeOverrideLabels will merge the existing labels and override by the labels passed in
    60  func MergeOverrideLabels(labels map[string]string) MutateOption {
    61  	return func(object metav1.Object) error {
    62  		util.AddLabels(object, labels)
    63  		return nil
    64  	}
    65  }
    66  
    67  // MergeOverrideAnnotations will merge the existing annotations and override by the annotations passed in
    68  func MergeOverrideAnnotations(annotations map[string]string) MutateOption {
    69  	return func(object metav1.Object) error {
    70  		util.AddAnnotations(object, annotations)
    71  		return nil
    72  	}
    73  }
    74  
    75  // MergeNoConflictLabels will merge the existing labels with the labels passed in, it will report conflicts if exists
    76  func MergeNoConflictLabels(labels map[string]string) MutateOption {
    77  	return func(object metav1.Object) error {
    78  		existingLabels := object.GetLabels()
    79  		// check and fill the labels
    80  		for k, v := range labels {
    81  			ev, ok := existingLabels[k]
    82  			if ok && ev != "" && ev != v {
    83  				return fmt.Errorf("%s for object %s, key: %s, conflicts value: %s <-> %s", velaerr.LabelConflict, object.GetName(), k, ev, v)
    84  			}
    85  			existingLabels[k] = v
    86  		}
    87  		object.SetLabels(existingLabels)
    88  		return nil
    89  	}
    90  }
    91  
    92  // CreateOrUpdateNamespace will create a namespace if not exist, it will also update a namespace if exists
    93  // It will report an error if the labels conflict while it will override the annotations
    94  func CreateOrUpdateNamespace(ctx context.Context, kubeClient client.Client, name string, options ...MutateOption) error {
    95  	ns, err := GetNamespace(ctx, kubeClient, name)
    96  	switch {
    97  	case err == nil:
    98  		return PatchNamespace(ctx, kubeClient, ns, options...)
    99  	case apierrors.IsNotFound(err):
   100  		return CreateNamespace(ctx, kubeClient, name, options...)
   101  	default:
   102  		return err
   103  	}
   104  }
   105  
   106  // PatchNamespace will patch a namespace
   107  func PatchNamespace(ctx context.Context, kubeClient client.Client, ns *corev1.Namespace, options ...MutateOption) error {
   108  	original := ns.DeepCopy()
   109  	for _, op := range options {
   110  		if err := op(ns); err != nil {
   111  			return err
   112  		}
   113  	}
   114  	return kubeClient.Patch(ctx, ns, client.MergeFrom(original))
   115  }
   116  
   117  // CreateNamespace will create a namespace with mutate option
   118  func CreateNamespace(ctx context.Context, kubeClient client.Client, name string, options ...MutateOption) error {
   119  	obj := &corev1.Namespace{
   120  		ObjectMeta: metav1.ObjectMeta{
   121  			Name: name,
   122  		},
   123  		Spec: corev1.NamespaceSpec{},
   124  	}
   125  	for _, op := range options {
   126  		if err := op(obj); err != nil {
   127  			return err
   128  		}
   129  	}
   130  	return kubeClient.Create(ctx, obj)
   131  }
   132  
   133  // GetNamespace will return a namespace with mutate option
   134  func GetNamespace(ctx context.Context, kubeClient client.Client, name string) (*corev1.Namespace, error) {
   135  	obj := &corev1.Namespace{}
   136  	err := kubeClient.Get(ctx, client.ObjectKey{Name: name}, obj)
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  	return obj, nil
   141  }
   142  
   143  // UpdateNamespace will update a namespace with mutate option
   144  func UpdateNamespace(ctx context.Context, kubeClient client.Client, name string, options ...MutateOption) error {
   145  	var namespace corev1.Namespace
   146  	err := kubeClient.Get(ctx, k8stypes.NamespacedName{Name: name}, &namespace)
   147  	if err != nil {
   148  		return err
   149  	}
   150  	for _, op := range options {
   151  		if err = op(&namespace); err != nil {
   152  			return err
   153  		}
   154  	}
   155  	return kubeClient.Update(ctx, &namespace)
   156  }
   157  
   158  // GetServiceAccountSubjectFromConfig extract ServiceAccountName subject from token
   159  func GetServiceAccountSubjectFromConfig(cfg *rest.Config) string {
   160  	sub, _ := GetTokenSubject(cfg.BearerToken)
   161  	return sub
   162  }
   163  
   164  // GetCertificateCommonNameAndOrganizationsFromConfig extract CommonName and Organizations from Certificate
   165  func GetCertificateCommonNameAndOrganizationsFromConfig(cfg *rest.Config) (string, []string) {
   166  	cert := cfg.CertData
   167  	if len(cert) == 0 && cfg.CertFile != "" {
   168  		cert, _ = os.ReadFile(cfg.CertFile)
   169  	}
   170  	name, _ := GetCertificateSubject(cert)
   171  	if name == nil {
   172  		return "", nil
   173  	}
   174  	return name.CommonName, name.Organization
   175  }
   176  
   177  // GetUserInfoFromConfig extract UserInfo from KubeConfig
   178  func GetUserInfoFromConfig(cfg *rest.Config) *authv1.UserInfo {
   179  	if sub := GetServiceAccountSubjectFromConfig(cfg); sub != "" {
   180  		return &authv1.UserInfo{Username: sub}
   181  	}
   182  	if cn, orgs := GetCertificateCommonNameAndOrganizationsFromConfig(cfg); cn != "" {
   183  		return &authv1.UserInfo{Username: cn, Groups: orgs}
   184  	}
   185  	return nil
   186  }
   187  
   188  // CreateOrUpdate create or update a kubernetes object
   189  func CreateOrUpdate(ctx context.Context, cli client.Client, obj client.Object) (controllerutil.OperationResult, error) {
   190  	bs, err := json.Marshal(obj)
   191  	if err != nil {
   192  		return controllerutil.OperationResultNone, err
   193  	}
   194  	return controllerutil.CreateOrUpdate(ctx, cli, obj, func() error {
   195  		createTimestamp := obj.GetCreationTimestamp()
   196  		resourceVersion := obj.GetResourceVersion()
   197  		deletionTimestamp := obj.GetDeletionTimestamp()
   198  		generation := obj.GetGeneration()
   199  		managedFields := obj.GetManagedFields()
   200  		if e := json.Unmarshal(bs, obj); err != nil {
   201  			return e
   202  		}
   203  		obj.SetCreationTimestamp(createTimestamp)
   204  		obj.SetResourceVersion(resourceVersion)
   205  		obj.SetDeletionTimestamp(deletionTimestamp)
   206  		obj.SetGeneration(generation)
   207  		obj.SetManagedFields(managedFields)
   208  		return nil
   209  	})
   210  }
   211  
   212  // EscapeResourceNameToLabelValue parse characters in resource name to label valid name
   213  func EscapeResourceNameToLabelValue(resourceName string) string {
   214  	return strings.ReplaceAll(resourceName, ":", "_")
   215  }
   216  
   217  // IsClusterScope check if the gvk is cluster scoped
   218  func IsClusterScope(gvk schema.GroupVersionKind, mapper meta.RESTMapper) (bool, error) {
   219  	mappings, err := mapper.RESTMappings(gvk.GroupKind(), gvk.Version)
   220  	isClusterScope := len(mappings) > 0 && mappings[0].Scope.Name() == meta.RESTScopeNameRoot
   221  	return isClusterScope, err
   222  }
   223  
   224  // GetPodsLogs get logs from pods
   225  func GetPodsLogs(ctx context.Context, config *rest.Config, containerName string, selectPods []*querytypes.PodBase, tmpl string, logC chan<- string, tailLines *int64) error {
   226  	if err := verifyPods(selectPods); err != nil {
   227  		return err
   228  	}
   229  	podRegex := getPodRegex(selectPods)
   230  	pods, err := regexp.Compile(podRegex)
   231  	if err != nil {
   232  		return fmt.Errorf("fail to compile '%s' for logs query", podRegex)
   233  	}
   234  	container := regexp.MustCompile(".*")
   235  	if containerName != "" {
   236  		container = regexp.MustCompile(containerName + ".*")
   237  	}
   238  	// These pods are from the same namespace, so we can use the first one to get the namespace
   239  	namespace := selectPods[0].Metadata.Namespace
   240  	selector := labels.NewSelector()
   241  	// Only use the labels to select pod if query one pod's log. It is only used when query vela-core log
   242  	if len(selectPods) == 1 {
   243  		for k, v := range selectPods[0].Metadata.Labels {
   244  			req, _ := labels.NewRequirement(k, selection.Equals, []string{v})
   245  			if req != nil {
   246  				selector = selector.Add(*req)
   247  			}
   248  		}
   249  	}
   250  	config.Wrap(multicluster.NewTransportWrapper())
   251  	clientSet, err := kubernetes.NewForConfig(config)
   252  	if err != nil {
   253  		return err
   254  	}
   255  	added, removed, err := stern.Watch(ctx,
   256  		clientSet.CoreV1().Pods(namespace),
   257  		pods,
   258  		container,
   259  		nil,
   260  		[]stern.ContainerState{stern.RUNNING, stern.TERMINATED},
   261  		selector,
   262  	)
   263  	if err != nil {
   264  		return err
   265  	}
   266  	tails := make(map[string]*stern.Tail)
   267  
   268  	funs := map[string]interface{}{
   269  		"json": func(in interface{}) (string, error) {
   270  			b, err := json.Marshal(in)
   271  			if err != nil {
   272  				return "", err
   273  			}
   274  			return string(b), nil
   275  		},
   276  		"color": func(color color.Color, text string) string {
   277  			return color.SprintFunc()(text)
   278  		},
   279  	}
   280  	template, err := template.New("log").Funcs(funs).Parse(tmpl)
   281  	if err != nil {
   282  		return errors.Wrap(err, "unable to parse template")
   283  	}
   284  
   285  	go func() {
   286  		for p := range added {
   287  			id := p.GetID()
   288  			if tails[id] != nil {
   289  				continue
   290  			}
   291  			// 48h
   292  			dur, _ := time.ParseDuration("48h")
   293  			tail := stern.NewTail(p.Namespace, p.Pod, p.Container, template, &stern.TailOptions{
   294  				Timestamps:   true,
   295  				SinceSeconds: int64(dur.Seconds()),
   296  				Exclude:      nil,
   297  				Include:      nil,
   298  				Namespace:    false,
   299  				TailLines:    tailLines, // default for all logs
   300  			})
   301  			tails[id] = tail
   302  
   303  			tail.Start(ctx, clientSet.CoreV1().Pods(p.Namespace), logC)
   304  		}
   305  	}()
   306  
   307  	go func() {
   308  		for p := range removed {
   309  			id := p.GetID()
   310  			if tails[id] == nil {
   311  				continue
   312  			}
   313  			tails[id].Close()
   314  			delete(tails, id)
   315  		}
   316  	}()
   317  
   318  	<-ctx.Done()
   319  	close(logC)
   320  	return nil
   321  }
   322  
   323  func getPodRegex(pods []*querytypes.PodBase) string {
   324  	var podNames []string
   325  	for _, pod := range pods {
   326  		podNames = append(podNames, fmt.Sprintf("(%s.*)", pod.Metadata.Name))
   327  	}
   328  	return strings.Join(podNames, "|")
   329  }
   330  
   331  func verifyPods(pods []*querytypes.PodBase) error {
   332  	if len(pods) == 0 {
   333  		return errors.New("no pods selected")
   334  	}
   335  	if len(pods) == 1 {
   336  		return nil
   337  	}
   338  	namespace := pods[0].Metadata.Namespace
   339  	for _, pod := range pods {
   340  		if pod.Metadata.Namespace != namespace {
   341  			return errors.New("cannot select pods from different namespaces")
   342  		}
   343  	}
   344  	return nil
   345  }
   346  
   347  // FilterObjectsByFieldSelector supports all field queries per type
   348  func FilterObjectsByFieldSelector(objects []runtime.Object, fieldSelector fields.Selector) []runtime.Object {
   349  	filterFunc := createFieldFilter(fieldSelector)
   350  	// selected matched ones
   351  	var filtered []runtime.Object
   352  	for _, object := range objects {
   353  		selected := true
   354  		if !filterFunc(object) {
   355  			selected = false
   356  		}
   357  
   358  		if selected {
   359  			filtered = append(filtered, object)
   360  		}
   361  	}
   362  	return filtered
   363  }
   364  
   365  // FilterFunc return true if object contains field selector
   366  type FilterFunc func(object runtime.Object) bool
   367  
   368  // createFieldFilter return filterFunc
   369  func createFieldFilter(selector fields.Selector) FilterFunc {
   370  	return func(object runtime.Object) bool {
   371  		return contains(&object, selector)
   372  	}
   373  }
   374  
   375  // implement a generic query filter to support multiple field selectors
   376  func contains(object *runtime.Object, fieldSelector fields.Selector) bool {
   377  	// call the ParseSelector function of "k8s.io/apimachinery/pkg/fields/selector.go" to validate and parse the selector
   378  	for _, requirement := range fieldSelector.Requirements() {
   379  		var negative bool
   380  		// supports '=', '==' and '!='.(e.g. ?fieldSelector=key1=value1,key2=value2)
   381  		// fields.ParseSelector(FieldSelector) has handled the case where the operator is '==' and converted it to '=',
   382  		// so case selection.DoubleEquals can be ignored here.
   383  		switch requirement.Operator {
   384  		case selection.NotEquals:
   385  			negative = true
   386  		case selection.Equals:
   387  			negative = false
   388  		default:
   389  			return false
   390  		}
   391  		key := requirement.Field
   392  		value := requirement.Value
   393  
   394  		data, err := json.Marshal(object)
   395  		if err != nil {
   396  			return false
   397  		}
   398  		result := gjson.Get(string(data), key)
   399  		if (negative && fmt.Sprintf("%v", result.String()) != value) ||
   400  			(!negative && fmt.Sprintf("%v", result.String()) == value) {
   401  			continue
   402  		}
   403  		return false
   404  	}
   405  	return true
   406  }