github.com/argoproj/argo-cd@v1.8.7/controller/cache/info.go (about)

     1  package cache
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/argoproj/gitops-engine/pkg/utils/kube"
     8  	"github.com/argoproj/gitops-engine/pkg/utils/text"
     9  	v1 "k8s.io/api/core/v1"
    10  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    11  	"k8s.io/apimachinery/pkg/runtime"
    12  	k8snode "k8s.io/kubernetes/pkg/util/node"
    13  
    14  	"github.com/argoproj/argo-cd/common"
    15  	"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
    16  	"github.com/argoproj/argo-cd/util/resource"
    17  )
    18  
    19  func populateNodeInfo(un *unstructured.Unstructured, res *ResourceInfo) {
    20  	gvk := un.GroupVersionKind()
    21  	revision := resource.GetRevision(un)
    22  	if revision > 0 {
    23  		res.Info = append(res.Info, v1alpha1.InfoItem{Name: "Revision", Value: fmt.Sprintf("Rev:%v", revision)})
    24  	}
    25  	switch gvk.Group {
    26  	case "":
    27  		switch gvk.Kind {
    28  		case kube.PodKind:
    29  			populatePodInfo(un, res)
    30  			return
    31  		case kube.ServiceKind:
    32  			populateServiceInfo(un, res)
    33  			return
    34  		}
    35  	case "extensions", "networking.k8s.io":
    36  		switch gvk.Kind {
    37  		case kube.IngressKind:
    38  			populateIngressInfo(un, res)
    39  			return
    40  		}
    41  	case "networking.istio.io":
    42  		switch gvk.Kind {
    43  		case "VirtualService":
    44  			populateIstioVirtualServiceInfo(un, res)
    45  			return
    46  		}
    47  	}
    48  
    49  	for k, v := range un.GetAnnotations() {
    50  		if strings.HasPrefix(k, common.AnnotationKeyLinkPrefix) {
    51  			if res.NetworkingInfo == nil {
    52  				res.NetworkingInfo = &v1alpha1.ResourceNetworkingInfo{}
    53  			}
    54  			res.NetworkingInfo.ExternalURLs = append(res.NetworkingInfo.ExternalURLs, v)
    55  		}
    56  	}
    57  }
    58  
    59  func getIngress(un *unstructured.Unstructured) []v1.LoadBalancerIngress {
    60  	ingress, ok, err := unstructured.NestedSlice(un.Object, "status", "loadBalancer", "ingress")
    61  	if !ok || err != nil {
    62  		return nil
    63  	}
    64  	res := make([]v1.LoadBalancerIngress, 0)
    65  	for _, item := range ingress {
    66  		if lbIngress, ok := item.(map[string]interface{}); ok {
    67  			if hostname := lbIngress["hostname"]; hostname != nil {
    68  				res = append(res, v1.LoadBalancerIngress{Hostname: fmt.Sprintf("%s", hostname)})
    69  			} else if ip := lbIngress["ip"]; ip != nil {
    70  				res = append(res, v1.LoadBalancerIngress{IP: fmt.Sprintf("%s", ip)})
    71  			}
    72  		}
    73  	}
    74  	return res
    75  }
    76  
    77  func populateServiceInfo(un *unstructured.Unstructured, res *ResourceInfo) {
    78  	targetLabels, _, _ := unstructured.NestedStringMap(un.Object, "spec", "selector")
    79  	ingress := make([]v1.LoadBalancerIngress, 0)
    80  	if serviceType, ok, err := unstructured.NestedString(un.Object, "spec", "type"); ok && err == nil && serviceType == string(v1.ServiceTypeLoadBalancer) {
    81  		ingress = getIngress(un)
    82  	}
    83  	res.NetworkingInfo = &v1alpha1.ResourceNetworkingInfo{TargetLabels: targetLabels, Ingress: ingress}
    84  }
    85  
    86  func populateIngressInfo(un *unstructured.Unstructured, res *ResourceInfo) {
    87  	ingress := getIngress(un)
    88  	targetsMap := make(map[v1alpha1.ResourceRef]bool)
    89  	if backend, ok, err := unstructured.NestedMap(un.Object, "spec", "backend"); ok && err == nil {
    90  		targetsMap[v1alpha1.ResourceRef{
    91  			Group:     "",
    92  			Kind:      kube.ServiceKind,
    93  			Namespace: un.GetNamespace(),
    94  			Name:      fmt.Sprintf("%s", backend["serviceName"]),
    95  		}] = true
    96  	}
    97  	urlsSet := make(map[string]bool)
    98  	if rules, ok, err := unstructured.NestedSlice(un.Object, "spec", "rules"); ok && err == nil {
    99  		for i := range rules {
   100  			rule, ok := rules[i].(map[string]interface{})
   101  			if !ok {
   102  				continue
   103  			}
   104  			host := rule["host"]
   105  			if host == nil || host == "" {
   106  				for i := range ingress {
   107  					host = text.FirstNonEmpty(ingress[i].Hostname, ingress[i].IP)
   108  					if host != "" {
   109  						break
   110  					}
   111  				}
   112  			}
   113  			paths, ok, err := unstructured.NestedSlice(rule, "http", "paths")
   114  			if !ok || err != nil {
   115  				continue
   116  			}
   117  			for i := range paths {
   118  				path, ok := paths[i].(map[string]interface{})
   119  				if !ok {
   120  					continue
   121  				}
   122  
   123  				if serviceName, ok, err := unstructured.NestedString(path, "backend", "serviceName"); ok && err == nil {
   124  					targetsMap[v1alpha1.ResourceRef{
   125  						Group:     "",
   126  						Kind:      kube.ServiceKind,
   127  						Namespace: un.GetNamespace(),
   128  						Name:      serviceName,
   129  					}] = true
   130  				}
   131  
   132  				stringPort := "http"
   133  				if tls, ok, err := unstructured.NestedSlice(un.Object, "spec", "tls"); ok && err == nil {
   134  					for i := range tls {
   135  						tlsline, ok := tls[i].(map[string]interface{})
   136  						secretName := tlsline["secretName"]
   137  						if ok && secretName != nil {
   138  							stringPort = "https"
   139  						}
   140  						tlshost := tlsline["host"]
   141  						if tlshost == host {
   142  							stringPort = "https"
   143  						}
   144  					}
   145  				}
   146  
   147  				externalURL := fmt.Sprintf("%s://%s", stringPort, host)
   148  
   149  				subPath := ""
   150  				if nestedPath, ok, err := unstructured.NestedString(path, "path"); ok && err == nil {
   151  					subPath = strings.TrimSuffix(nestedPath, "*")
   152  				}
   153  				externalURL += subPath
   154  				urlsSet[externalURL] = true
   155  			}
   156  		}
   157  	}
   158  	targets := make([]v1alpha1.ResourceRef, 0)
   159  	for target := range targetsMap {
   160  		targets = append(targets, target)
   161  	}
   162  
   163  	var urls []string
   164  	if res.NetworkingInfo != nil {
   165  		urls = res.NetworkingInfo.ExternalURLs
   166  	}
   167  	for url := range urlsSet {
   168  		urls = append(urls, url)
   169  	}
   170  	res.NetworkingInfo = &v1alpha1.ResourceNetworkingInfo{TargetRefs: targets, Ingress: ingress, ExternalURLs: urls}
   171  }
   172  
   173  func populateIstioVirtualServiceInfo(un *unstructured.Unstructured, res *ResourceInfo) {
   174  	targetsMap := make(map[v1alpha1.ResourceRef]bool)
   175  
   176  	if rules, ok, err := unstructured.NestedSlice(un.Object, "spec", "http"); ok && err == nil {
   177  		for i := range rules {
   178  			rule, ok := rules[i].(map[string]interface{})
   179  			if !ok {
   180  				continue
   181  			}
   182  			routes, ok, err := unstructured.NestedSlice(rule, "route")
   183  			if !ok || err != nil {
   184  				continue
   185  			}
   186  			for i := range routes {
   187  				route, ok := routes[i].(map[string]interface{})
   188  				if !ok {
   189  					continue
   190  				}
   191  
   192  				if hostName, ok, err := unstructured.NestedString(route, "destination", "host"); ok && err == nil {
   193  					hostSplits := strings.Split(hostName, ".")
   194  					serviceName := hostSplits[0]
   195  
   196  					var namespace string
   197  					if len(hostSplits) >= 2 {
   198  						namespace = hostSplits[1]
   199  					} else {
   200  						namespace = un.GetNamespace()
   201  					}
   202  
   203  					targetsMap[v1alpha1.ResourceRef{
   204  						Kind:      kube.ServiceKind,
   205  						Name:      serviceName,
   206  						Namespace: namespace,
   207  					}] = true
   208  				}
   209  			}
   210  		}
   211  	}
   212  	targets := make([]v1alpha1.ResourceRef, 0)
   213  	for target := range targetsMap {
   214  		targets = append(targets, target)
   215  	}
   216  
   217  	res.NetworkingInfo = &v1alpha1.ResourceNetworkingInfo{TargetRefs: targets}
   218  }
   219  
   220  func populatePodInfo(un *unstructured.Unstructured, res *ResourceInfo) {
   221  	pod := v1.Pod{}
   222  	err := runtime.DefaultUnstructuredConverter.FromUnstructured(un.Object, &pod)
   223  	if err != nil {
   224  		return
   225  	}
   226  	restarts := 0
   227  	totalContainers := len(pod.Spec.Containers)
   228  	readyContainers := 0
   229  
   230  	reason := string(pod.Status.Phase)
   231  	if pod.Status.Reason != "" {
   232  		reason = pod.Status.Reason
   233  	}
   234  
   235  	imagesSet := make(map[string]bool)
   236  	for _, container := range pod.Spec.InitContainers {
   237  		imagesSet[container.Image] = true
   238  	}
   239  	for _, container := range pod.Spec.Containers {
   240  		imagesSet[container.Image] = true
   241  	}
   242  
   243  	res.Images = nil
   244  	for image := range imagesSet {
   245  		res.Images = append(res.Images, image)
   246  	}
   247  
   248  	initializing := false
   249  	for i := range pod.Status.InitContainerStatuses {
   250  		container := pod.Status.InitContainerStatuses[i]
   251  		restarts += int(container.RestartCount)
   252  		switch {
   253  		case container.State.Terminated != nil && container.State.Terminated.ExitCode == 0:
   254  			continue
   255  		case container.State.Terminated != nil:
   256  			// initialization is failed
   257  			if len(container.State.Terminated.Reason) == 0 {
   258  				if container.State.Terminated.Signal != 0 {
   259  					reason = fmt.Sprintf("Init:Signal:%d", container.State.Terminated.Signal)
   260  				} else {
   261  					reason = fmt.Sprintf("Init:ExitCode:%d", container.State.Terminated.ExitCode)
   262  				}
   263  			} else {
   264  				reason = "Init:" + container.State.Terminated.Reason
   265  			}
   266  			initializing = true
   267  		case container.State.Waiting != nil && len(container.State.Waiting.Reason) > 0 && container.State.Waiting.Reason != "PodInitializing":
   268  			reason = "Init:" + container.State.Waiting.Reason
   269  			initializing = true
   270  		default:
   271  			reason = fmt.Sprintf("Init:%d/%d", i, len(pod.Spec.InitContainers))
   272  			initializing = true
   273  		}
   274  		break
   275  	}
   276  	if !initializing {
   277  		restarts = 0
   278  		hasRunning := false
   279  		for i := len(pod.Status.ContainerStatuses) - 1; i >= 0; i-- {
   280  			container := pod.Status.ContainerStatuses[i]
   281  
   282  			restarts += int(container.RestartCount)
   283  			if container.State.Waiting != nil && container.State.Waiting.Reason != "" {
   284  				reason = container.State.Waiting.Reason
   285  			} else if container.State.Terminated != nil && container.State.Terminated.Reason != "" {
   286  				reason = container.State.Terminated.Reason
   287  			} else if container.State.Terminated != nil && container.State.Terminated.Reason == "" {
   288  				if container.State.Terminated.Signal != 0 {
   289  					reason = fmt.Sprintf("Signal:%d", container.State.Terminated.Signal)
   290  				} else {
   291  					reason = fmt.Sprintf("ExitCode:%d", container.State.Terminated.ExitCode)
   292  				}
   293  			} else if container.Ready && container.State.Running != nil {
   294  				hasRunning = true
   295  				readyContainers++
   296  			}
   297  		}
   298  
   299  		// change pod status back to "Running" if there is at least one container still reporting as "Running" status
   300  		if reason == "Completed" && hasRunning {
   301  			reason = "Running"
   302  		}
   303  	}
   304  
   305  	if pod.DeletionTimestamp != nil && pod.Status.Reason == k8snode.NodeUnreachablePodReason {
   306  		reason = "Unknown"
   307  	} else if pod.DeletionTimestamp != nil {
   308  		reason = "Terminating"
   309  	}
   310  
   311  	if reason != "" {
   312  		res.Info = append(res.Info, v1alpha1.InfoItem{Name: "Status Reason", Value: reason})
   313  	}
   314  	res.Info = append(res.Info, v1alpha1.InfoItem{Name: "Containers", Value: fmt.Sprintf("%d/%d", readyContainers, totalContainers)})
   315  	res.NetworkingInfo = &v1alpha1.ResourceNetworkingInfo{Labels: un.GetLabels()}
   316  }