istio.io/istio@v0.0.0-20240520182934-d79c90f27776/istioctl/pkg/util/handlers/handlers.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package handlers
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"sort"
    22  	"strings"
    23  	"time"
    24  
    25  	corev1 "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/labels"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
    30  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    31  	"k8s.io/kubectl/pkg/polymorphichelpers"
    32  	"k8s.io/kubectl/pkg/util/podutils"
    33  	gatewayapi "sigs.k8s.io/gateway-api/apis/v1"
    34  	gatewayapibeta "sigs.k8s.io/gateway-api/apis/v1beta1"
    35  
    36  	"istio.io/istio/pilot/pkg/config/kube/gateway"
    37  	"istio.io/istio/pkg/config/constants"
    38  	kubelib "istio.io/istio/pkg/kube"
    39  )
    40  
    41  // InferPodInfo Uses name to infer namespace if the passed name contains namespace information.
    42  // Otherwise uses the namespace value passed into the function
    43  func InferPodInfo(name, defaultNS string) (string, string) {
    44  	return inferNsInfo(name, defaultNS)
    45  }
    46  
    47  // inferNsInfo Uses name to infer namespace if the passed name contains namespace information.
    48  // Otherwise uses the namespace value passed into the function
    49  func inferNsInfo(name, namespace string) (string, string) {
    50  	if idx := strings.LastIndex(name, "/"); idx > 0 {
    51  		// If there is a / in it, we need to handle differently. This is resourcetype/name.namespace.
    52  		// However, resourcetype can have . in it as well, so we should only look for namespace after the /.
    53  		separator := strings.LastIndex(name[idx:], ".")
    54  		if separator < 0 {
    55  			return name, namespace
    56  		}
    57  
    58  		return name[0 : idx+separator], name[idx+separator+1:]
    59  	}
    60  	separator := strings.LastIndex(name, ".")
    61  	if separator < 0 {
    62  		return name, namespace
    63  	}
    64  
    65  	return name[0:separator], name[separator+1:]
    66  }
    67  
    68  func InferPodsFromTypedResource(name, defaultNS string, factory cmdutil.Factory) ([]string, string, error) {
    69  	resname, ns := inferNsInfo(name, defaultNS)
    70  	if !strings.Contains(resname, "/") {
    71  		return []string{resname}, ns, nil
    72  	}
    73  	client, podName, namespace, selector, err := getClientForResource(resname, ns, factory)
    74  	if err != nil {
    75  		return []string{}, "", err
    76  	}
    77  	if podName != "" {
    78  		return []string{podName}, namespace, err
    79  	}
    80  
    81  	options := metav1.ListOptions{LabelSelector: selector}
    82  	podList, err := client.Pods(namespace).List(context.TODO(), options)
    83  	if err != nil {
    84  		return []string{}, "", err
    85  	}
    86  	pods := []string{}
    87  	for i := range podList.Items {
    88  		pods = append(pods, podList.Items[i].Name)
    89  	}
    90  
    91  	return pods, namespace, nil
    92  }
    93  
    94  func getClientForResource(resname, ns string, factory cmdutil.Factory) (*corev1client.CoreV1Client, string, string, string, error) {
    95  	// Pod is referred to using something like "deployment/httpbin".  Use the kubectl
    96  	// libraries to look up the resource name, find the pods it selects, and return
    97  	// one of those pods.
    98  	builder := factory.NewBuilder().
    99  		WithScheme(kubelib.IstioScheme, kubelib.IstioScheme.PrioritizedVersionsAllGroups()...).
   100  		NamespaceParam(ns).DefaultNamespace().
   101  		SingleResourceType()
   102  	builder.ResourceNames("pods", resname)
   103  	infos, err := builder.Do().Infos()
   104  	if err != nil {
   105  		return nil, "", "", "", fmt.Errorf("failed retrieving: %v in the %q namespace", err, ns)
   106  	}
   107  	if len(infos) != 1 {
   108  		return nil, "", "", "", errors.New("expected a resource")
   109  	}
   110  	_, ok := infos[0].Object.(*corev1.Pod)
   111  	if ok {
   112  		// If we got a pod, just use its name
   113  		return nil, infos[0].Name, infos[0].Namespace, "", nil
   114  	}
   115  	namespace, selector, err := SelectorsForObject(infos[0].Object)
   116  	if err != nil {
   117  		return nil, "", "", "", fmt.Errorf("%q does not refer to a pod: %v", resname, err)
   118  	}
   119  	clientConfig, err := factory.ToRESTConfig()
   120  	if err != nil {
   121  		return nil, "", "", "", err
   122  	}
   123  	clientset, err := corev1client.NewForConfig(clientConfig)
   124  	if err != nil {
   125  		return nil, "", "", "", err
   126  	}
   127  	return clientset, "", namespace, selector.String(), nil
   128  }
   129  
   130  // this is used for testing. it should not be changed in regular code.
   131  var getFirstPodFunc func(client corev1client.PodsGetter, namespace string, selector string, timeout time.Duration,
   132  	sortBy func([]*corev1.Pod) sort.Interface) (*corev1.Pod, int, error)
   133  
   134  // InferPodInfoFromTypedResource gets a pod name, from an expression like Deployment/httpbin, or Deployment/productpage-v1.bookinfo
   135  func InferPodInfoFromTypedResource(name, defaultNS string, factory cmdutil.Factory) (string, string, error) {
   136  	resname, ns := inferNsInfo(name, defaultNS)
   137  	if !strings.Contains(resname, "/") {
   138  		return resname, ns, nil
   139  	}
   140  	client, podName, namespace, selector, err := getClientForResource(resname, ns, factory)
   141  	if err != nil {
   142  		return "", "", err
   143  	}
   144  	if podName != "" {
   145  		return podName, namespace, nil
   146  	}
   147  	// We need to pass in a sorter, and the one used by `kubectl logs` is good enough.
   148  	sortBy := func(pods []*corev1.Pod) sort.Interface { return podutils.ByLogging(pods) }
   149  	timeout := 2 * time.Second
   150  	if getFirstPodFunc == nil {
   151  		getFirstPodFunc = polymorphichelpers.GetFirstPod
   152  	}
   153  	pod, _, err := getFirstPodFunc(client, namespace, selector, timeout, sortBy)
   154  	if err != nil {
   155  		return "", "", fmt.Errorf("no pods match %q", resname)
   156  	}
   157  	return pod.Name, namespace, nil
   158  }
   159  
   160  // SelectorsForObject is a fork of upstream function to add additional Istio type support
   161  func SelectorsForObject(object runtime.Object) (namespace string, selector labels.Selector, err error) {
   162  	switch t := object.(type) {
   163  	case *gatewayapi.Gateway:
   164  		if !gateway.IsManaged(&t.Spec) {
   165  			return "", nil, fmt.Errorf("gateway is not a managed gateway")
   166  		}
   167  		namespace = t.Namespace
   168  		selector, err = labels.Parse(constants.GatewayNameLabel + "=" + t.Name)
   169  	case *gatewayapibeta.Gateway:
   170  		if !gateway.IsManaged(&t.Spec) {
   171  			return "", nil, fmt.Errorf("gateway is not a managed gateway")
   172  		}
   173  		namespace = t.Namespace
   174  		selector, err = labels.Parse(constants.GatewayNameLabel + "=" + t.Name)
   175  	default:
   176  		return polymorphichelpers.SelectorsForObject(object)
   177  	}
   178  	return
   179  }