blob: 108bc991d1d45b6708794a5fea97ff51ca938beb [file]
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You 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 cmd
import (
"context"
"fmt"
"io"
"sort"
"strings"
"text/tabwriter"
"time"
"github.com/apache/dubbo-kubernetes/cli/pkg/cli"
"github.com/apache/dubbo-kubernetes/pkg/kube/inject"
"github.com/kdubbo/api/annotation"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/duration"
"k8s.io/client-go/kubernetes"
)
type getPodsArgs struct {
namespace string
}
func GetCmd(ctx cli.Context) *cobra.Command {
command := &cobra.Command{
Use: "get",
Short: "Display Dubbo resources",
}
command.AddCommand(getPodsCmd(ctx))
return command
}
func getPodsCmd(ctx cli.Context) *cobra.Command {
args := &getPodsArgs{}
command := &cobra.Command{
Use: "pods",
Aliases: []string{"pod", "po"},
Short: "List pods injected by Dubbo",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
client, err := ctx.CLIClient()
if err != nil {
return err
}
pods, err := listInjectedPods(cmd.Context(), client.Kube(), args.namespace)
if err != nil {
return err
}
return printInjectedPods(cmd.OutOrStdout(), pods, time.Now())
},
}
command.Flags().StringVarP(&args.namespace, "namespace", "n", "", "Namespace to scan; empty scans all namespaces")
return command
}
func listInjectedPods(ctx context.Context, client kubernetes.Interface, namespace string) ([]corev1.Pod, error) {
listNamespace := namespace
if listNamespace == "" {
listNamespace = metav1.NamespaceAll
}
pods, err := client.CoreV1().Pods(listNamespace).List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err
}
out := make([]corev1.Pod, 0, len(pods.Items))
for _, pod := range pods.Items {
if isInjectedPod(pod) {
out = append(out, pod)
}
}
sort.SliceStable(out, func(i, j int) bool {
if out[i].Namespace == out[j].Namespace {
return out[i].Name < out[j].Name
}
return out[i].Namespace < out[j].Namespace
})
return out, nil
}
func isInjectedPod(pod corev1.Pod) bool {
if pod.Annotations[annotation.OrgApacheDubboProxylessStatus.Name] != "" {
return true
}
if containsCSV(pod.Annotations[annotation.OrgApacheDubboInjectTemplates.Name], inject.ProxylessGRPCTemplateName) {
return true
}
if hasVolume(pod, inject.ProxylessXDSVolumeName) {
return true
}
for _, container := range allContainers(pod) {
if container.Name == inject.ProxylessGRPCInboundContainerName {
return true
}
for _, env := range container.Env {
switch env.Name {
case "GRPC_XDS_BOOTSTRAP", inject.ProxylessGRPCConfigEnvName, inject.ProxylessXDSAddressEnvName:
return true
}
}
}
return false
}
func printInjectedPods(writer io.Writer, pods []corev1.Pod, now time.Time) error {
table := tabwriter.NewWriter(writer, 0, 8, 2, ' ', 0)
if _, err := fmt.Fprintln(table, "NAMESPACE\tNAME\tREADY\tSTATUS\tRESTARTS\tAGE\tIP\tNODE\tTEMPLATES"); err != nil {
return err
}
for _, pod := range pods {
if _, err := fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%d\t%s\t%s\t%s\t%s\n",
pod.Namespace,
pod.Name,
readyContainers(pod),
podDisplayStatus(pod),
restartCount(pod),
duration.HumanDuration(now.Sub(pod.CreationTimestamp.Time)),
pod.Status.PodIP,
pod.Spec.NodeName,
podTemplates(pod),
); err != nil {
return err
}
}
return table.Flush()
}
func allContainers(pod corev1.Pod) []corev1.Container {
out := make([]corev1.Container, 0, len(pod.Spec.InitContainers)+len(pod.Spec.Containers))
out = append(out, pod.Spec.InitContainers...)
out = append(out, pod.Spec.Containers...)
return out
}
func hasVolume(pod corev1.Pod, name string) bool {
for _, volume := range pod.Spec.Volumes {
if volume.Name == name {
return true
}
}
return false
}
func containsCSV(raw, want string) bool {
for _, item := range strings.Split(raw, ",") {
if strings.TrimSpace(item) == want {
return true
}
}
return false
}
func readyContainers(pod corev1.Pod) string {
ready := 0
for _, status := range pod.Status.ContainerStatuses {
if status.Ready {
ready++
}
}
return fmt.Sprintf("%d/%d", ready, len(pod.Spec.Containers))
}
func restartCount(pod corev1.Pod) int32 {
var count int32
for _, status := range pod.Status.InitContainerStatuses {
count += status.RestartCount
}
for _, status := range pod.Status.ContainerStatuses {
count += status.RestartCount
}
return count
}
func podDisplayStatus(pod corev1.Pod) string {
if pod.DeletionTimestamp != nil {
return "Terminating"
}
for _, status := range pod.Status.ContainerStatuses {
if status.State.Waiting != nil && status.State.Waiting.Reason != "" {
return status.State.Waiting.Reason
}
if status.State.Terminated != nil && status.State.Terminated.Reason != "" {
return status.State.Terminated.Reason
}
}
if pod.Status.Phase == "" {
return "Unknown"
}
return string(pod.Status.Phase)
}
func podTemplates(pod corev1.Pod) string {
if templates := strings.TrimSpace(pod.Annotations[annotation.OrgApacheDubboInjectTemplates.Name]); templates != "" {
return templates
}
if isInjectedPod(pod) {
return inject.ProxylessGRPCTemplateName
}
return ""
}